美文网首页java学习之路
leetCode进阶算法题+解析(五十六)

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

作者: 唯有努力不欺人丶 | 来源:发表于2020-11-04 17:24 被阅读0次

    最近工作还算是清闲,也不用加班什么的,所以有了大把时间刷题。直接开始吧。

    随即翻转矩形

    题目:题中给出一个 n_rows 行 n_cols 列的二维矩阵,且所有值被初始化为 0。要求编写一个 flip 函数,均匀随机的将矩阵中的 0 变为 1,并返回该值的位置下标 [row_id,col_id];同样编写一个 reset 函数,将所有的值都重新置为 0。尽量最少调用随机函数 Math.random(),并且优化时间和空间复杂度。
    注意:
    1 <= n_rows, n_cols <= 10000
    0 <= row.id < n_rows 并且 0 <= col.id < n_cols
    当矩阵中没有值为 0 时,不可以调用 flip 函数
    调用 flip 和 reset 函数的次数加起来不会超过 1000 次

    示例 1:
    输入:
    ["Solution","flip","flip","flip","flip"]
    [[2,3],[],[],[],[]]
    输出: [null,[0,1],[1,2],[1,0],[1,1]]
    示例 2:
    输入:
    ["Solution","flip","flip","reset","flip"]
    [[1,2],[],[],[],[]]
    输出: [null,[0,0],[0,1],null,[0,0]]
    输入语法解释:
    输入包含两个列表:被调用的子程序和他们的参数。Solution 的构造函数有两个参数,分别为 n_rows 和 n_cols。flip 和 reset 没有参数,参数总会以列表形式给出,哪怕该列表为空

    思路:这个题怎么说呢,我好想是没太看懂。而且这里说最少用random。最少就一次呗。先用我的理解去写写代码试试吧。这种题目本来就是开放的。
    好了,第一版本代码出来了,我先贴代码:

    class Solution {
        Set<String> set;
        int r;
        int c;
        public Solution(int n_rows, int n_cols) {
            set = new HashSet<String>();
            r = n_rows;
            c = n_cols;
        }
        
        public int[] flip() {
            int rr = new Random().nextInt(r);
            int cc = new Random().nextInt(c);
            while(set.contains(rr+","+cc)){
                rr = new Random().nextInt(r);
                cc = new Random().nextInt(c);
            }
            set.add(rr+","+cc);
            return new int[]{rr,cc};
        }
        
        public void reset() {
            set.clear();
        }
    }
    
    /**
     * Your Solution object will be instantiated and called as such:
     * Solution obj = new Solution(n_rows, n_cols);
     * int[] param_1 = obj.flip();
     * obj.reset();
     */
    

    没啥技术含量,毕竟这种开放的题目对付实现就完事了。但是我估计肯定是有更合适的做法。我去看下题解吧。

    class Solution {
    
        Map<Integer, Integer> V = new HashMap<>();
        int nr, nc, rem;
        Random rand = new Random();
    
        public Solution(int n_rows, int n_cols) {
            nr = n_rows;
            nc = n_cols;
            rem = nr * nc;
        }
    
        public int[] flip() {
            int r = rand.nextInt(rem--);
            int x = V.getOrDefault(r, r);
            V.put(r, V.getOrDefault(rem, rem));
            return new int[]{x / nc, x % nc};
        }
    
        public void reset() {
            V.clear();
            rem = nr * nc;
        }
    }
    

    我还特意去百度代码中的一个方法:

    getOrDefault(key,  defaultValue)
    

    意味如果有这个key,则返回key对应的value。如果没有这个key,则返回给定的defaultValue.简单来说上面题目的思路是做Map映射,如果key不存在,则取并且从尾部取值对应到Key上,如果key存在,取对应的Value,并且把随机范围减一。这样做到用一维的map来维护了n*c个元素的二维数组。
    这个题我写的挺无脑的,答案挺精巧的。总而言之是个挺有意思的题。学到的是一种新的思维方式。就是map映射做跳表的思路。。下一题。

    最长特殊序列2

    题目:给定字符串列表,你需要从它们中找出最长的特殊序列。最长特殊序列定义如下:该序列为某字符串独有的最长子序列(即不能是其他字符串的子序列)。子序列可以通过删去字符串中的某些字符实现,但不能改变剩余字符的相对顺序。空序列为所有字符串的子序列,任何字符串为其自身的子序列。输入将是一个字符串列表,输出是最长特殊序列的长度。如果最长特殊序列不存在,返回 -1 。

    示例:
    输入: "aba", "cdc", "eae"
    输出: 3
    提示:
    所有给定的字符串长度不会超过 10 。
    给定字符串列表的长度将在 [2, 50 ] 之间。

    思路:这个题怎么说呢,本身序列的题就比较复杂。毕竟不是字面量,可以跳元素。你就看着限制条件。字符串长度不会超过十。总数小于50就能看出计算有多复杂。反正这种题目第一反应是dp没啥问题。至于状态转移方程我的慢慢去琢磨。大概的思路是每一个和其余的比找出特殊的序列。然后这些特殊的继续往下比,应该是个二维数组。我去试试吧。
    算了,dp个屁,我还是先用自己的方式对付实现了再说吧。就暴力破解了。
    直接贴代码:

    class Solution {
        public int findLUSlength(String[] strs) {
            Arrays.sort(strs, new Comparator<String>() {
                @Override
                public int compare(String o1, String o2) {
                    return o1.length()>o2.length()?-1:1;
                }
            });
            if(strs[0].length()>strs[1].length()) return strs[0].length();
            for(int i = 0;i<strs.length;i++){
                String res = strs[i];
                for(int j = 0;j<strs.length;j++){
                    if(i==j) continue;
                    res = getDiff(res, strs[j]);
                    //说明这个元素没有自己的子序列
                    if("".equals(res)) break;
                }
                if(!"".equals(res)) return res.length();
            }
            return -1;
        }
        public String getDiff(String a,String b) {
            char[] aa = a.toCharArray();
            int idx = 0;
            for(char c:b.toCharArray()) {
                //这个元素有了继续往下对比
                if(c==aa[idx]) {
                    //如果最后一个元素都比对完了说明这个序列不是唯一的。返回空
                    if(idx==aa.length-1) return "";
                    idx++;
                }
            }
            //到这还没返回说明这个子序列 b中没有,暂时算是特殊的
            return a;
        }
    }
    

    我简直呵呵了,性能百分百过的。我为什么会觉得这道题要dp?怕不是脑子进了水了。生生想了半个多小时到怀疑人生。
    这个题简单的很,思路主要是:一个串中有特殊序列,那么这整个串一定是特殊的。有点心累,不想多说话,下一题吧。

    连续的子序组合

    题目:给定一个包含 非负数 的数组和一个目标 整数 k,编写一个函数来判断该数组是否含有连续的子数组,其大小至少为 2,且总和为 k 的倍数,即总和为 n*k,其中 n 也是一个整数。

    示例 1:
    输入:[23,2,4,6,7], k = 6
    输出:True
    解释:[2,4] 是一个大小为 2 的子数组,并且和为 6。
    示例 2:
    输入:[23,2,6,4,7], k = 6
    输出:True
    解释:[23,2,6,4,7]是大小为 5 的子数组,并且和为 42。
    说明:
    数组的长度不会超过 10,000 。
    你可以认为所有数字总和在 32 位有符号整数范围内。

    思路:这个题感觉最暴力的就是每一个数开始往后加。但是感觉暴力法会有问题。但是我要去试试。
    第一版本暴力法解出来了,我先贴代码:

    class Solution {
        public boolean checkSubarraySum(int[] nums, int k) {
            if(k==0) {
                for(int i=0;i<nums.length-1;i++){
                    if(nums[i] == nums[i+1] && nums[i] == 0) return true;
                }
                return false;
            }
            for(int i = 0;i<nums.length;i++) {
                int sum = nums[i];
                for(int j = i+1;j<nums.length;j++) {
                    sum += nums[j];
                    if(sum%k == 0) return true;
                }
            }
            return false;
        }
    }
    

    怎么说呢,现在越来越懒得写题解了,感觉有的题目比较简单,没啥好说的,太难的又说不明白。反正这道题面向测试案例编程。给出的结果中0,0 k=0的话也是true。所以有了第一个key=0的特殊处理。性能超过百分之五十。估计是有更好的做法。但是我没精力想了,直接去看性能排行第一的代码:

    class Solution {
        public boolean checkSubarraySum(int[] nums, int k) {
            Map<Integer, Integer> map = new HashMap<>();
            map.put(0, -1);
            int sum = 0, n = nums.length;
            for (int i = 0; i < n; i++) {
                sum += nums[i];
                int v = k != 0 ? sum % k : sum == 0 ? 0 : sum;
                Integer o = map.put(v, i);
                if (o != null) {
                    if (i - o > 1) return true;
                    map.put(v, o);
                }
            }
            return false;
        }
    }
    

    这个题的题解我觉得超级赞的一个思路。但是人家官方的说法太不好理解了。我自己的想法就是:

    sum%k = n.   (sum+(x+y+z+...))%k =n。
    

    我是不是可以理解x+y+z+...本身被k整除了?只要确定这个x.y,z..的数量大于2,就说明出现了能大于2的k的倍数(这个题坑的一点是哪怕1,-1或者0,0这种也算正确答案。)反正数学我也就这样了,什么取模对数啥的也心有余力不足了。用自己的方式能理解就挺好了。下一题。

    通过删除字母匹配到字典里最长单词

    题目:给定一个字符串和一个字符串字典,找到字典里面最长的字符串,该字符串可以通过删除给定字符串的某些字符来得到。如果答案不止一个,返回长度最长且字典顺序最小的字符串。如果答案不存在,则返回空字符串。

    示例 1:
    输入:
    s = "abpcplea", d = ["ale","apple","monkey","plea"]
    输出:
    "apple"
    示例 2:
    输入:
    s = "abpcplea", d = ["a","b","c"]
    输出:
    "a"
    说明:
    所有输入的字符串只包含小写字母。
    字典的大小不会超过 1000。
    所有输入的字符串长度不会超过 1000。

    思路:第一反应就是判断这个单词是不是给定字符串的子序列。其实也就是暴力遍历法。找到最长的那个子序列。相同长度取排位靠前的。就是这样,我去试试能不能过。
    翻车的地方莫名其妙。字典顺序最小的字符串。我本来以为是在数组中排名靠前的呢。我去把这一块改了
    贴代码:

    class Solution {
        public String findLongestWord(String s, List<String> d) {
            String res = "";
            int max = 0;
            for(String dd:d) {
                if(isOk(s, dd)) {
                    //是子序列,直接取最大的
                    if(max<dd.length()) {
                        max = dd.length();
                        res = dd;
                    }else if(max == dd.length()) {//相同长度取字典顺序最小的
                        res = dd.compareTo(res)<0?dd:res;
                    }
                }
            }
            return res;
        }
        //判断是不是子序列
        public boolean isOk(String s,String d) {
            char[] c = d.toCharArray();
            int idx = 0;
            for(char ss : s.toCharArray()) {
                if(ss==c[idx]) {
                    idx++;
                    //d中所有元素s中都有,所以直接返回true
                    if(idx == c.length) return true;
                }
            }
            return false;
        }
    }
    

    这个没什么好说的。就是暴力破解。用到了两个方法:一个是判断一个字符串是不是另一个字符串的子序列。还有就是字符串字典顺序比较。我这里直接调用的内置函数compareTo。想自己写就拆开比较就ok了,也没啥难度就是比较墨迹、这个题就这样了,下一题。

    连续数组

    题目:给定一个二进制数组, 找到含有相同数量的 0 和 1 的最长连续子数组(的长度)。

    示例 1:
    输入: [0,1]
    输出: 2
    说明: [0, 1] 是具有相同数量0和1的最长连续子数组。
    示例 2:
    输入: [0,1,0]
    输出: 2
    说明: [0, 1] (或 [1, 0]) 是具有相同数量0和1的最长连续子数组。
    注意: 给定的二进制数组的长度不会超过50000。

    思路:不知道为啥这个题没有任何时间空间的约束。反正暴力法就是数组最长才5w。我的想法就是建立一个list(因为不定长所以不用数组了)。然后0的出现个数统计,1的出现个数统计。就这样往下统计下去。最后遍历list找到连续两个数的较小的值中的最大值。说起来比较绕,但是我觉得实现起来还是很容易的。我去试试了。
    好吧,是我没理解题意。这里0,1,0,1我以为结果是2.但是没想到这样是4,所以上面的思路推翻重试。暴力法就不合适了。我想想怎么实现吧。
    我觉得我思路贼精巧,还好这两道题挨在一起了所以找到了思路。但是性能不咋地。我先附上代码:

    class Solution {
        public int findMaxLength(int[] nums) {
            for(int i = 0;i<nums.length;i++){
                if(nums[i]==0) nums[i]=-1;
            }
            //找到和为0的最长子序列(和上一道题类似)
            Map<Integer, Integer> map = new HashMap<Integer, Integer>();
            map.put(0,-1);//默认就是0了。
            int sum = 0;
            int max = 0;
            for(int i = 0;i<nums.length;i++){
                sum += nums[i];
                //当再次出现和是sum,说明中间累加这么多和为0.也就是1和-1数目相等
                if(map.containsKey(sum)) {
                    max = Math.max(max, i-map.get(sum));
                }else {
                    map.put(sum,i);//第一次出现和是sum的情况。
                }
            }
            return max;
        }
    

    上一道题也是这个思路,我觉得我注释写的比较清楚了。真的这两道题安排在一起帮了我。我再去看看性能排行第一的代码:

    class Solution {
        public int findMaxLength(int[] nums) {
            if (null == nums || nums.length < 2) {
                return 0;
            }
    
            int length = nums.length;
            int mappingLength = 2 * length + 1;
            int[] mapping = new int[mappingLength];
            for (int i = 0; i < mappingLength; i++) {
                mapping[i] = -2;
            }
            mapping[length] = -1;
    
            int maxLength = 0;
            int count = 0;
            for (int i = 0; i < length; i++) {
                count += 2 * nums[i] - 1;
                if (mapping[count + length] >= -1) {
                    maxLength = Math.max(maxLength, i - mapping[count + length]);
                } else {
                    mapping[count + length] = i;
                }
            }
            return maxLength;
        }
    }
    

    本质上和我的思路是一样的。不过我把0赋值为-1了。而这里用的是2 * nums[i] - 1、算出来还是1就+1.0就-1。
    区别就是我用的map存储。而这里用数组存储了。其实我觉得就是用空间换时间嘛。不是很惊喜。这道题就这样了。
    这篇笔记就记到这里,如果稍微帮到你了记得点个喜欢点个关注。也祝大家工作顺顺利利!生活愉快!

    相关文章

      网友评论

        本文标题:leetCode进阶算法题+解析(五十六)

        本文链接:https://www.haomeiwen.com/subject/oetyvktx.html