leetCode进阶算法题+解析(七十六)

知识像个⚪,里面是我们所知的,外面是未知的。知道的越多,我们会发现未知的也越多。今日份自勉语句。

链表组件

题目:给定链表头结点 head,该链表上的每个结点都有一个 唯一的整型值 。同时给定列表 G,该列表是上述链表中整型值的一个子集。返回列表 G 中组件的个数,这里对组件的定义为:链表中一段最长连续结点的值(该值必须在列表 G 中)构成的集合。

示例 1:
输入:
head: 0->1->2->3
G = [0, 1, 3]
输出: 2
解释:
链表中,0 和 1 是相连接的,且 G 中不包含 2,所以 [0, 1] 是 G 的一个组件,同理 [3] 也是一个组件,故返回 2。
示例 2:
输入:
head: 0->1->2->3->4
G = [0, 3, 1, 4]
输出: 2
解释:
链表中,0 和 1 是相连接的,3 和 4 是相连接的,所以 [0, 1] 和 [3, 4] 是两个组件,故返回 2。
提示:
如果 N 是给定链表 head 的长度,1 <= N <= 10000。
链表中每个结点的值所在范围为 [0, N - 1]。
1 <= G.length <= 10000
G 是链表中所有结点的值的一个子集.

思路:这个题的思路我暂时的想法就是把G用set记录。然後从头遍历链表。用一个计数器count来记录。如果当前节点G中存在则count+1。不存在并且count不为0则ans+1,且count归0。。暂时就是这样,我去代码实现试试.
好了,ac了,上第一版本代码:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public int numComponents(ListNode head, int[] G) {
        Set<Integer> set = new HashSet<Integer>();
        for(int i : G) set.add(i);
        int count = 0;
        int ans = 0;
        while(head != null) {
            if(set.contains(head.val)) {
                count++;
            }else {
                if(count != 0) ans++;
                count = 0;
            }
            head = head.next;
        }
        if(count != 0) ans++;
        return ans;
    }
}

这个题怎么说呢,比较简单,简单难度撑死了,不知道怎么混到中等难度里的.然後一开始我忘记了结尾count不为0也要算所以wa了一次.我这个代码性能也还好,O(n)时间复杂度,O(1)空间复杂度.我去看看性能第一的代码,没什么大问题的话就过了:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public int numComponents(ListNode head, int[] G) {
        boolean hash[] = new boolean[10000];
        for(int i = 0; i < G.length; i++){
            hash[G[i]] = true;
        } 
        int res = 0; //记录组件的个数
        while(head != null){
            if(hash[head.val]){
                while(head.next != null && hash[head.val]){
                    head = head.next;
                }
                res++;
            }           
            head = head.next;
        }
        return res;
    }
}

这个用的空间换时间?反正用了hash数组,性能变好了能理解.这个题就这样吧.

单词的压缩编码

题目:单词数组 words 的 有效编码 由任意助记字符串 s 和下标数组 indices 组成,且满足:
words.length == indices.length
助记字符串 s 以 '#' 字符结尾
对于每个下标 indices[i] ,s 的一个从 indices[i] 开始、到下一个 '#' 字符结束(但不包括 '#')的 子字符串 恰好与 words[i] 相等
给你一个单词数组 words ,返回成功对 words 进行编码的最小助记字符串 s 的长度 。

示例 1:
输入:words = ["time", "me", "bell"]
输出:10
解释:一组有效编码为 s = "time#bell#" 和 indices = [0, 2, 5] 。
words[0] = "time" ,s 开始于 indices[0] = 0 到下一个 '#' 结束的子字符串,如加粗部分所示 "time#bell#"
words[1] = "me" ,s 开始于 indices[1] = 2 到下一个 '#' 结束的子字符串,如加粗部分所示 "time#bell#"
words[2] = "bell" ,s 开始于 indices[2] = 5 到下一个 '#' 结束的子字符串,如加粗部分所示 "time#bell#"
示例 2:
输入:words = ["t"]
输出:2
解释:一组有效编码为 s = "t#" 和 indices = [0] 。
提示:
1 <= words.length <= 2000
1 <= words[i].length <= 7
words[i] 仅由小写字母组成

思路:这个题我觉得首先看单词之间的关系.比如time和me.这种time的结尾包含me的.但是因为单词总共2000个..所以这种包含关系我觉得还是挺不好算的.我打算先暴力法试试.首先第一个单词看有没有endwith第一个单词的,没有的话则先把第一个单词放这.然後一个字符一个字符的减.看后面有没有同等的单词,有的话直接算在这里.思路还算清楚.就是怕超时.我去试试代码了.又想出一些算是优化的措施:比如可以单词按照长度分组.
暴力法居然ac了,哈哈,我直接贴代码:

class Solution {
    public int minimumLengthEncoding(String[] words) {
        Set<String> set1 = new HashSet<String>();
        Set<String> set2 = new HashSet<String>();
        Set<String> set3 = new HashSet<String>();
        Set<String> set4 = new HashSet<String>();
        Set<String> set5 = new HashSet<String>();
        Set<String> set6 = new HashSet<String>();
        Set<String> set7 = new HashSet<String>();
        for(String s : words) {
            switch (s.length()) {
            case 1: set1.add(s); break;
            case 2: set2.add(s); break;
            case 3: set3.add(s); break;
            case 4: set4.add(s); break;
            case 5: set5.add(s); break;
            case 6: set6.add(s); break;
            default: set7.add(s);
            }
        }
        int ans = 0;
        for(String s: set7) {
            ans += 8;
            set6.remove(s.substring(1, 7));
            set5.remove(s.substring(2, 7));
            set4.remove(s.substring(3, 7));
            set3.remove(s.substring(4, 7));
            set2.remove(s.substring(5, 7));
            set1.remove(s.substring(6, 7));
        }
        for(String s: set6) {
            ans += 7;
            set5.remove(s.substring(1, 6));
            set4.remove(s.substring(2, 6));
            set3.remove(s.substring(3, 6));
            set2.remove(s.substring(4, 6));
            set1.remove(s.substring(5, 6));
        }
        for(String s: set5) {
            ans += 6;
            set4.remove(s.substring(1, 5));
            set3.remove(s.substring(2, 5));
            set2.remove(s.substring(3, 5));
            set1.remove(s.substring(4, 5));
        }
        for(String s: set4) {
            ans += 5;
            set3.remove(s.substring(1, 4));
            set2.remove(s.substring(2, 4));
            set1.remove(s.substring(3, 4));
        }
        for(String s: set3) {
            ans += 4;
            set2.remove(s.substring(1, 3));
            set1.remove(s.substring(2, 3));
        }
        for(String s: set2) {
            ans += 3;
            set1.remove(s.substring(1, 2));
        }
        for(String s: set1) {
            ans += 2;
        }       
        return ans;
    }
}

思路和我上面说的差不多.然後重点是四个字符的单词可能包含3个的,2个的,1个的.但是不可能包含四个的(set自动去重,一样的不占用长度).所以说这个代码写的比较丑,但是思路还是挺清晰的.而且性能并没有我想的那么惨不忍睹.其实我现在觉得没必要分这么多set,应该一个也能搞定.我去试试代码:

class Solution {
    public int minimumLengthEncoding(String[] words) {
        Set<String> good = new HashSet<String>(Arrays.asList(words));
        for (String word: words) {
            for (int k = 1; k < word.length(); ++k) {
                good.remove(word.substring(k));
            }
        }

        int ans = 0;
        for (String word: good) {
            ans += word.length() + 1;
        }
        return ans;
    }
}

下面我去看看性能第一的代码吧:
只能说不出所料,其实在做的时候我就想过字典树了,还寻思暴力法超时了就换..但是因为没超时,emmmm...总而言之思路就是每一个单词从后往前.最后一个字母是字典的开始.每次都从后往前遍历,如果找到一样的子分支了则说明当前的不需要额外加长.如果找到包含的子分支并且这个分支不够长,那么把之前的单词算在这个单词的一部分,所以要续上的是当前大于之前的那个长度.最后如果找不到类似的子分支则这个新挂在树上,思路还算好理解,下面是大佬的代码实现:

class Solution {
    public int minimumLengthEncoding(String[] words) {
        Trie trie = new Trie();
        int ans = 0;
        for (String word: words) {
            ans += trie.insert(word);
        }
        return ans;
    }
}

class TrieNode {
    TrieNode[] next;
    boolean end;

    public TrieNode() {
        this.next = new TrieNode[26];
        this.end = false;
    }
}

class Trie {
    TrieNode node;

    public Trie() {
        this.node = new TrieNode();
    }

    public int insert(String word) {
        char[] chs = word.toCharArray();
        int len = chs.length;
        TrieNode cur = this.node;
        int ans = len + 1;
        boolean update = false;
        for (int i = len - 1; i >= 0; i--) {
            int p = chs[i] - 'a';
            if (cur.end) {
                cur.end = false;
                // common suffix
                ans -= len - i;
            }
            if (cur.next[p] == null) {
                cur.next[p] = new TrieNode();
                update = true;
            }
            cur = cur.next[p];
        }
        if (update) {
            cur.end = true;
            return ans;
        } else {
            return 0;
        }
    }
}

这个题解出来不算难,但是怎么优雅的解出来还是挺不容易的.下一题.

翻转卡片游戏

题目:在桌子上有 N 张卡片,每张卡片的正面和背面都写着一个正数(正面与背面上的数有可能不一样)。
我们可以先翻转任意张卡片,然后选择其中一张卡片。如果选中的那张卡片背面的数字 X 与任意一张卡片的正面的数字都不同,那么这个数字是我们想要的数字。哪个数是这些想要的数字中最小的数(找到这些数中的最小值)呢?如果没有一个数字符合要求的,输出 0。其中, fronts[i] 和 backs[i] 分别代表第 i 张卡片的正面和背面的数字。如果我们通过翻转卡片来交换正面与背面上的数,那么当初在正面的数就变成背面的数,背面的数就变成正面的数。

示例:
输入:fronts = [1,2,4,4,7], backs = [1,3,4,1,3]
输出:2
解释:假设我们翻转第二张卡片,那么在正面的数变成了 [1,3,4,4,7] , 背面的数变成了 [1,2,4,1,3]。
接着我们选择第二张卡片,因为现在该卡片的背面的数是 2,2 与任意卡片上正面的数都不同,所以 2 就是我们想要的数字。
提示:
1 <= fronts.length == backs.length <= 1000
1 <= fronts[i] <= 2000
1 <= backs[i] <= 2000

思路:讲真,这个 题目我自己读了好几遍都没看懂,还是问了群里的人才算是理解题意了.本来我很奇怪为什么是2而不是1.后来发现是因为这个背面的值,要和正面所有的值都不同(包括自己这张的正面).所以1pass了.理解完题意以后说这个题的解题思路:首先正负面一样的元素直接pass.其次因为数字最大2千.我暂时的想法是2000个元素数组.然後正负面一样的直接false.剩下的从前往后一个个数判断.我去试试代码.
第一版本代码:

class Solution {
    public int flipgame(int[] fronts, int[] backs) {
        int[] d = new int[2001];//2是false。肯定不行了。1是有可能,可以试试。 0是不存在这个元素
        for(int i = 0;i<fronts.length;i++) {
            if(fronts[i] == backs[i]) {
                d[fronts[i]] = 2;
            }else {
                if(d[fronts[i]] != 2) d[fronts[i]] = 1;
                if(d[backs[i]] != 2) d[backs[i]] = 1;
            }           
        }
        int min = 2001;
        for(int i = 0;i<2001;i++) {
            if(d[i] == 0 || d[i] == 2) continue;
            return i;
        }
        return 0;       
    }
}

说真的,我觉得这个题难度在于阅读理解吧?反正做出来挺容易的,比我想的还简单.然後性能也不错,我再去看看性能第一的代码:

class Solution {
    public int flipgame(int[] fronts, int[] backs) {
        
        int[] arr = new int[2000];
        int i =0;
        while(i < fronts.length){
            if(fronts[i] == backs[i]){
                arr[fronts[i]] = 1;
            }
            i++;
        }
        int j=0;
        int min = Integer.MAX_VALUE;
        while(j < fronts.length){
            int test = fronts[j];
            if(arr[test] ==0 && test < min){
                min = test;
            }
            int test2 = backs[j];
            if(arr[test2] ==0 && test2 < min){
                min = test2;
            }
            j++;
        }

        if(min < Integer.MAX_VALUE){
            return min;
        }else{
            return 0;
        }
    }
}

感觉这个代码的好处是不用多加很多无用的判断.比如我这边数组是2000个.但是如果最大数是4.可是5-2000我还要判断一遍的.然後这个解法就不用这个判断了.除了这个也没啥了.下一题吧.

带因子的二叉树

题目:给出一个含有不重复整数元素的数组,每个整数均大于 1。我们用这些整数来构建二叉树,每个整数可以使用任意次数。其中:每个非叶结点的值应等于它的两个子结点的值的乘积。满足条件的二叉树一共有多少个?返回的结果应模除 10 ** 9 + 7。

示例 1:
输入: A = [2, 4]
输出: 3
解释: 我们可以得到这些二叉树: [2], [4], [4, 2, 2]
示例 2:
输入: A = [2, 4, 5, 10]
输出: 7
解释: 我们可以得到这些二叉树: [2], [4], [5], [10], [4, 2, 2], [10, 2, 5], [10, 5, 2].
提示:
1 <= A.length <= 1000.
2 <= A[i] <= 10 ^ 9.

思路:这个题首先因为每个整数都大于1.因为不存在等于1的情况,所以我们可以从最高层开始找.虽然这个没有标签,但是我觉得应该可以用dp解决.每一个数字本身都可以单独作为一个树.所以开始就是1.然後比如4可以被2乘2来组成,所以4在1的基础上+1.也就是4的组成个数是2.所以2.4这个数组的答案是3.重点是如果三层树:20->4->2这种.4本身就两种可能,一种是单独的4一种是422.5的系数是1.20的结果就是20本身的1+(1乘2)乘2 = 5.之所以(1乘2)乘2是因为左右分支两种可能.如果是2,2或者4,4这种就不用再乘2了.大概思路是这样,我去代码试试.
第一版本代码:

class Solution {
    public int numFactoredBinaryTrees(int[] arr) {
        Arrays.sort(arr);
        Map<Integer, Long> map = new HashMap<Integer, Long>();
        for(int i:arr) map.put(i, 1l);//都可以单独成树,所以是1
        for(int i = 1;i<arr.length;i++) {
            int temp = arr[i];
            Set<Integer> set = new HashSet<>();
            for(int j = i-1;j>=0;j--) {                
                int cur = arr[j];
                if(set.contains(cur)) continue;
                if(temp%cur == 0 && map.containsKey(temp/cur)) {                   
                    if(temp/cur == cur) {
                        map.put(temp, map.get(temp)+map.get(cur)*map.get(cur)); 
                    }else {
                        set.add(temp/cur);
                        map.put(temp,map.get(temp)+map.get(cur)*map.get(temp/cur)*2);
                    }
                }
            }
        }
        long ans = 0;
        for(long i:map.values()) ans += i;
        return (int)(ans%1000000007);
    }
}

我只能说勉强过了.低空掠过.因为这个数据太大,我只想到了map.各种map,set查找.感觉性能是耗费在这里.但是优化一时半会儿想不出好办法...我直接去看看性能第一的代码:

class Solution {
    public final static int MOD = 1000000007;
    public int numFactoredBinaryTrees(int[] arr) {
        Arrays.sort(arr);
        Map<Integer,Integer> map = new HashMap<>() ;
        for(int i = 0;i < arr.length;i++){
            map.put(arr[i],i) ;
        }
        long[] dp = new long[arr.length] ;
        long ans = 0 ;
        for(int i = 0;i < arr.length;i++){
            double sq = Math.sqrt(arr[i]) ;
            dp[i] = 1 ;
            for(int j = 0;j < i;j++){
                if(arr[j] > sq){
                    break;
                }
                if(arr[i]%arr[j] == 0){
                    Integer other = map.get(arr[i]/arr[j]) ;
                    if(other != null){
                        dp[i] = (dp[i]+dp[j]*dp[other]%MOD)%MOD ;
                        if(other != j){
                            dp[i] = (dp[i]+dp[j]*dp[other]%MOD)%MOD ;
                        }
                    }
                }
            }
            ans += dp[i] ;
        }
        return (int) (ans%MOD);
    }
}

这个平方根,着实优秀!!都踏马是我知道的知识,看了人家的代码恍然大悟,自己就是没想起来..脑壳痛,反正这个题思路差不多就是这样.然後细节的处理优化在于平方根往上不用看了.因为如果有在这边较小的数字上也处理完了.这个题就这样了,下一题.

适龄的朋友

题目:人们会互相发送好友请求,现在给定一个包含有他们年龄的数组,ages[i] 表示第 i 个人的年龄。当满足以下任一条件时,A 不能给 B(A、B不为同一人)发送好友请求:
age[B] <= 0.5 * age[A] + 7
age[B] > age[A]
age[B] > 100 && age[A] < 100
否则,A 可以给 B 发送好友请求。注意如果 A 向 B 发出了请求,不等于 B 也一定会向 A 发出请求。而且,人们不会给自己发送好友请求。 求总共会发出多少份好友请求?

示例 1:
输入:[16,16]
输出:2
解释:二人可以互发好友申请。
示例 2:
输入:[16,17,18]
输出:2
解释:好友请求可产生于 17 -> 16, 18 -> 17.
示例 3:
输入:[20,30,100,110,120]
输出:3
解释:好友请求可产生于 110 -> 100, 120 -> 110, 120 -> 100.
提示:
1 <= ages.length <= 20000
1 <= ages[i] <= 120

首先说题目:我感觉这个题有点问题啊:感觉条件2和条件三重复了.当然这个不重要.继续说题目.我的想法是每个人只要看小于等于当前区间的.然後我打算年龄最大120.作为一个数组.然後下标代表年龄,值代表人数.直接计算.没啥好说的,我去写代码试试.
第一版本代码:

class Solution {
    public int numFriendRequests(int[] ages) {
        int[] d = new int[121];
        for(int i :ages) d[i]++;
        int ans = 0;
        for(int i = 0;i<d.length;i++) {
            if(d[i] == 0) continue;
            if(d[i] > 1 && i>14) ans += d[i]*(d[i]-1);
            double temp = 0.5*i+7;
            for(int j = i-1;j>=0;j--) {
                if(d[j] == 0) continue;
                if(j<=temp) break;//太小了不合适
                ans += d[i] * d[j]; 
            }
        }
        return ans;
    }
}

这个代码一开始i>14我没写,所以卡了一下,剩下的没啥特别难的.就是判断就行了,和预计的也差不读.这个题就这样了,因为性能已经不错了所以不看性能第一的代码啦.
本篇笔记就记到这里,如果稍微帮到你了记得点个喜欢点个关注,也祝大家工作顺顺利利!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容