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

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

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

    最近沉迷扫雷,无法自拔。恍然一看这周还没刷题,心都碎了。今天开始要赶进度了,开始刷题。

    01矩阵

    题目:给定一个由 0 和 1 组成的矩阵,找出每个元素到最近的 0 的距离。两个相邻元素间的距离为 1 。

    示例 1:
    输入:
    [[0,0,0],
    [0,1,0],
    [0,0,0]]
    输出:
    [[0,0,0],
    [0,1,0],
    [0,0,0]]
    示例 2:
    输入:
    [[0,0,0],
    [0,1,0],
    [1,1,1]]
    输出:
    [[0,0,0],
    [0,1,0],
    [1,2,1]]
    提示:
    给定矩阵的元素个数不超过 10000。
    给定矩阵中至少有一个元素是 0。
    矩阵中的元素只在四个方向上相邻: 上、下、左、右。

    思路:这个题的思路还是很清晰的,我觉得就是一层一层的过。直到所有的元素都填满。因为没有任何时间空间要求,我先去用暴力法试试超不超时。
    有点小神奇,居然ac了并且性能超过百分之九十九,哈哈。先贴代码:

    class Solution {
        public int[][] updateMatrix(int[][] matrix) {
            int n = 0; //记录一共多少要该的数字
            for(int i = 0;i<matrix.length;i++) {
                for(int j = 0;j<matrix[0].length;j++) {
                    if(matrix[i][j]==1) {
                        n++;
                        matrix[i][j] = -1;
                    }
                }
            }
            int temp = 0;
            while(n>0) {//说明还有要该的数字,所以继续循环
                for(int i = 0;i<matrix.length;i++) {
                    for(int j = 0;j<matrix[0].length;j++) {
                        //这个元素开始四外扩散.因为从小的开始扩散,所以先赋值的肯定是最小的
                        if(matrix[i][j]==temp) {
                            if(i>0 && matrix[i-1][j] == -1) {
                                matrix[i-1][j] = temp+1; 
                                n--;
                            }
                            if(i<matrix.length-1 && matrix[i+1][j] == -1) {
                                matrix[i+1][j]  = temp+1;
                                n--;
                            }
                            if(j>0 && matrix[i][j-1] == -1) {
                                matrix[i][j-1] = temp+1;
                                n--;
                            }
                            if(j<matrix[0].length-1 && matrix[i][j+1] == -1) {
                                matrix[i][j+1] = temp+1;
                                n--;
                            }
                        }
                    }
                }
                //第一遍填充0周围的,第二次填充1周围的。。。依次类推,直到都填完
                temp++;
            }       
            return matrix;
        }
    }
    

    这个题就像我之前说的思路差不多,一层一层的往外填充,最终没有可填充元素就说明都填完路径了,跳出循环return就好了。其实这个题挺有意思,哪怕思路清晰代码也比较麻烦。我挺喜欢这种的,不是一眼就能看出答案,但是又不是死活想不出来。哈哈。下一题了。

    朋友圈

    题目:班上有 N 名学生。其中有些人是朋友,有些则不是。他们的友谊具有是传递性。如果已知 A 是 B 的朋友,B 是 C 的朋友,那么我们可以认为 A 也是 C 的朋友。所谓的朋友圈,是指所有朋友的集合。给定一个 N * N 的矩阵 M,表示班级中学生之间的朋友关系。如果M[i][j] = 1,表示已知第 i 个和 j 个学生互为朋友关系,否则为不知道。你必须输出所有学生中的已知的朋友圈总数。

    示例 1:
    输入:
    [[1,1,0],
    [1,1,0],
    [0,0,1]]
    输出:2
    解释:已知学生 0 和学生 1 互为朋友,他们在一个朋友圈。
    第2个学生自己在一个朋友圈。所以返回 2 。
    示例 2:
    输入:
    [[1,1,0],
    [1,1,1],
    [0,1,1]]
    输出:1
    解释:已知学生 0 和学生 1 互为朋友,学生 1 和学生 2 互为朋友,所以学生 0 和学生 2 也是朋友,所以他们三个在一个朋友圈,返回 1 。
    提示:
    1 <= N <= 200
    M[i][i] == 1
    M[i][j] == M[j][i]

    思路:找个语文好的翻译这么难么?这个题我反复读了几遍才懂。总而言之这个题叫深搜还是光搜啥的来这,反正是有思路,而且本质上就数组长度个人,应该不难,我去试试。
    这个性能我绝望了,自我感觉良好,现实给我一巴掌。先贴代码:

    class Solution {
        public int findCircleNum(int[][] M) { 
            int n = 0;
            for(int i = 0;i<M.length;i++) {
                if(M[i][i] == 1) {//说明这个人没被纳入圈子,可以单独开圈
                    n++;
                    M[i][i] = -1;
                    //把和这个能一个圈的都纳入
                    dfs(M, i);
                }
            }       
            return n;
        }
        public void dfs(int[][] M,int i) {      
            //如果跟这个有关系说明是一个圈子
            for(int k = 0;k<M.length;k++) {
                if(M[i][k] == 1) {
                    M[i][k] = -1;
                    M[k][k] = -1;
                    //M[i][k] = 1 说明i和k有关系,k进入i的圈子.同时k圈子里的也都进入
                    dfs(M, k);
                }
            }
        }
    }
    

    这个其实就是每个同学只能有一个圈子,所以有圈子的不用管了。所有没圈子的判断一遍就好了。感觉性能不应该这样啊,我去看看性能排行第一的代码吧。
    看了半天解析没太懂,据说这个是查并集的题,我反正贴上题解下一题了:

    class Solution {
        // DFS
        // public int findCircleNum(int[][] M) {
        //     if (M == null || M.length == 0) {
        //         return 0;
        //     }
        //     int count = 0;
        //     boolean[] visited = new boolean[M.length];
        //     for (int i = 0; i < M.length; i++) {
        //         if (!visited[i]) {
        //             dfs(M, visited, i);
        //             ++ count;
        //         }
        //     }
        //     return count;
        // }
        // public void dfs(int[][] M, boolean[] visited, int i) {
        //     for (int j = 0; j < M.length; j++) {
        //         if (M[i][j] == 1 && !visited[j]) {
        //             visited[j] = true;
        //             dfs(M, visited, j);
        //         }
        //     }
        // }
    
        // BFS second
        // public int findCircleNum(int[][] M) {
        //     if (M == null || M.length == 0) {
        //         return 0;
        //     }
        //     int length = M.length;
        //     int count = 0;
        //     boolean[] visited = new boolean[length];
        //     for (int i = 0; i < length; i++) {
        //         if (!visited[i]) {
        //             bfs(M, visited, i);
        //             count++;
        //         }
        //     }
        //     return count;
        // }
        // public void bfs(int[][] M, boolean[] visited, int i) {
        //     for (int j = 0; j < M.length; j++) {
        //         if (!visited[j] && M[i][j] == 1) {
        //             visited[j] = true;
        //             bfs(M, visited, j);
        //         }
        //     }
        // }
    
        // 并查集 错误
        // public int findCircleNum(int[][] M) {
        //     if (M == null || M.length == 0) {
        //         return 0;
        //     }
        //     int[] parent = new int[M.length];
        //     for (int i = 0; i < M.length; i++) {
        //         parent[i] = i;
        //     }
        //     for (int i = 0; i < M.length; i++) {
        //         for (int j = i + 1; j < M.length; j++) {
        //             if (M[i][j] == 1) {
        //                 parent = union(M, parent, i, j);
        //             }
        //         }
        //     }
        //     Set<Integer> set = new HashSet();
        //     for (int p : parent) {
        //         set.add(p);
        //     }
        //     return set.size();
        // }
        // public int[] union(int[][] M, int[] parent, int i, int j) {
        //     int pi = parent(M, parent, i);
        //     int pj = parent(M, parent, j);
        //     parent[pj] = pi;
        //     return parent;
        // }
        // public int parent(int[][] M, int[] parent, int i) {
        //     int result = i;
        //     while (parent[result] != result) {
        //         parent[result] = parent[parent[result]];
        //         result = parent[result];
        //     }
        //     while (parent[i] != i) {
        //         int temp = i;
        //         i = parent[i];
        //         parent[temp] = result;
        //     }
        //     return result;
        // }
    
        // 并查集
        // public int findCircleNum(int[][] M) {
        //     if (M == null || M.length == 0) {
        //         return 0;
        //     }
        //     int n = M.length;
        //     Union union = new Union(n);
        //     for (int i = 0; i < n; i++) {
        //         for (int j = i + 1; j < n; j++) {
        //             if (M[i][j] == 1) {
        //                 union.union(M, i, j);
        //             }
        //         }
        //     }
        //     return union.getCount();
        // }
    
        // class Union {
        //     private int count;
        //     private int[] parent;
        //     public Union(int n) {
        //         count = n;
        //         parent = new int[n];
        //         for (int i = 0; i < n; i++) {
        //             parent[i] = i;
        //         }
        //     }
    
        //     public void union(int[][] M, int i, int j) {
        //         int pi = find(M, i);
        //         int pj = find(M, j);
        //         if (pi == pj) {
        //             return;
        //         }
        //         parent[pj] = pi;
        //         count --;
        //     }
    
        //     public int find(int[][] M, int i) {
        //         int result = i;
        //         while (parent[result] != result) {
        //             parent[result] = parent[parent[result]];
        //             result = parent[result];
        //         }
        //         return result;
        //     }
    
        //     public int getCount() {
        //         return count;
        //     }
        // }
    
        // 并查集 second
        // public int findCircleNum(int[][] M) {
        //     if (M == null || M.length == 0) {
        //         return 0;
        //     }
        //     int n = M.length;
        //     Union u = new Union(n);
        //     for (int i = 0; i < n; i++) {
        //         for (int j = i + 1; j < n; j++) {
        //             if (M[i][j] == 1) {
        //                 u.union(M, i, j);
        //             }
        //         }
        //     }
        //     return u.count();
        // }
    
        // class Union {
        //     private int count;
        //     private int[] p;
    
        //     public Union(int n) {
        //         this.count = n;
        //         p = new int[n];
        //         for (int i = 0; i < n; i++) {
        //             p[i] = i;
        //         }
        //     }
    
        //     public void union(int[][] M, int i, int j) {
        //         int pi = this.parent(i);
        //         int pj = this.parent(j);
        //         if (pi == pj) {
        //             return;
        //         }
        //         p[pj] = pi;
        //         count --;
        //     }
    
        //     public int parent(int i) {
        //         while(p[i] != i) {
        //             i = p[p[i]];
        //         }
        //         return i;
        //     }
    
        //     public int count() {
        //         return count;
        //     }
        // }
    
        public int findCircleNum(int[][] M) {
            if (M == null || M.length == 0) {
                return 0;
            }
            int n = M.length;
            Union u = new Union(n);
            for (int i = 0; i < n; i++) {
                for (int j = i + 1; j < n; j++) {
                    if (M[i][j] == 1) {
                        u.union(i, j);
                    }
                }
            }
            return u.count();
        }
    
        class Union {
            private int n;
            private int count;
            private int[] p;
    
            public Union(int n) {
                this.n = n;
                this.count = n;
                p = new int[n];
                for (int i = 0; i < n; i++) {
                    p[i] = i;
                }
            }
            public void union(int i, int j) {
                int pi = findParent(i);
                int pj = findParent(j);
                if (pi != pj) {
                    count--;
                    p[pj] = pi;
                }
            }
            public int findParent(int i) {
                while(p[i] != i) {
                    i = p[p[i]];
                }
                return i;
            }
            public int count() {
                return count;
            }
        }
    
    }
    

    (ps:吐槽一下写上面代码的大佬,确实厉害,好几个版本。)下一题了。

    最优除法

    题目:给定一组正整数,相邻的整数之间将会进行浮点除法操作。例如, [2,3,4] -> 2 / 3 / 4 。但是,你可以在任意位置添加任意数目的括号,来改变算数的优先级。你需要找出怎么添加括号,才能得到最大的结果,并且返回相应的字符串格式的表达式。你的表达式不应该含有冗余的括号。

    示例:
    输入: [1000,100,10,2]
    输出: "1000/(100/10/2)"
    解释:
    1000/(100/10/2) = 1000/((100/10)/2) = 200
    但是,以下加粗的括号 "1000/((100/10)/2)" 是冗余的,
    因为他们并不影响操作的优先级,所以你需要返回 "1000/(100/10/2)"。
    其他用例:
    1000/(100/10)/2 = 50
    1000/(100/(10/2)) = 50
    1000/100/10/2 = 0.5
    1000/100/(10/2) = 2
    说明:
    输入数组的长度在 [1, 10] 之间。
    数组中每个元素的大小都在 [2, 1000] 之间。
    每个测试用例只有一个最优除法解。

    思路:这个题怎么说呢,本来看了名字我都不想做了,以为又是位操作的那种呢,不过审完题觉得是我想错了。其实这个题的思路我觉得应该挺清晰的。每个数字都是正整数。而除法来讲,被除数.除数,商的关系。我感觉这个题是dp啊,不知道感觉对不对。尤其是最多只有10个数。。是不是说明这个暴力法会超时?我一一去试试。
    我可能是个憨憨,在那寻思半天,真正的要做了的时候才发现个问题。这个题就三者。第一是被除数,第二个除数。第三个是商。想要商最大就是被除数最大,除数最小就行了。因为都是正整数。所以肯定是越除越小。直接第一个数除后面所有相除的结果,这个题简单到离谱。。直接贴代码:

    class Solution {
        public String optimalDivision(int[] nums) {
            StringBuffer sb = new StringBuffer();
            sb.append(nums[0]);
            int len = nums.length;
            if(len == 1) return sb.toString();
            if(len == 2) return sb.append("/"+nums[1]).toString();   
            sb.append("/(");     
            for(int i = 1;i<len-1;i++) sb.append(nums[i]+"/");
            return sb.append(nums[len-1]+")").toString();
        }
    }
    

    我这个其实是好几次改版的结果了,感觉这个题就是一个性能比拼,我去看看性能第一的代码怎么处理的吧:

    class Solution { // 1000/((100/10)/2) 这样就是最大的
        public String optimalDivision(int[] nums) {
    
            StringBuilder sb = new StringBuilder();
            sb.append(nums[0]);
            if (nums.length == 1) return sb.toString();
            sb.append('/');
            if (nums.length == 2) {
                sb.append(nums[1]);
                return sb.toString();
            }
            sb.append('(');
            for (int i = 1; i < nums.length; i++) {
                sb.append(nums[i]);
                sb.append('/');
            }
            sb.deleteCharAt(sb.length() - 1);
            sb.append(')');
    
            return sb.toString();
        }
    }
    

    纯粹的代码性能优化,不多说了,下一题。

    砖墙

    题目:你的面前有一堵矩形的、由多行砖块组成的砖墙。 这些砖块高度相同但是宽度不同。你现在要画一条自顶向下的、穿过最少砖块的垂线。砖墙由行的列表表示。 每一行都是一个代表从左至右每块砖的宽度的整数列表。如果你画的线只是从砖块的边缘经过,就不算穿过这块砖。你需要找出怎样画才能使这条线穿过的砖块数量最少,并且返回穿过的砖块数量。你不能沿着墙的两个垂直边缘之一画线,这样显然是没有穿过一块砖的。
    题目截图

    思路:这个题挺有意思。其实思路是有的,比如说二维数组。但是那样明显性能有问题。所以我现在的想法是怎么不用二维数组实现这个计算。初步的想法很多。不管是map还是set其实都可以实现一个记录。每一个墙缝都记录。这样这一层不在墙缝的就要穿。实现一个记录。但是最多的话1w1w的计算量是少不了了。哪怕剪枝也不见得能ac,我先去试试吧。*
    完美的超时了,所以这个果然不行。我再去看看。反着思路能不能好一点。一定是从一个墙缝开始算的。有了墙缝才可能会少于给定长度。
    我也不知道今天是什么邪,做出来每一个性能好的,我直接贴代码:

    class Solution {
        public int leastBricks(List<List<Integer>> wall) {
            //list下标代表层数,值代表这层的墙缝
            List<Set<Integer>> list = new ArrayList<Set<Integer>>();
            int wide = 0;
            for(int i = 0;i<wall.size();i++) {
                Set<Integer> set = new HashSet<Integer>();
                int sum = 0;
                for(int j =0;j<wall.get(i).size();j++) {
                    sum = sum +wall.get(i).get(j);
                    set.add(sum);
                }
                list.add(set);
                wide = sum;
            }
            int min = list.size();
            int max = 0;
            Map<Integer, Integer> map = new HashMap<Integer, Integer>();
            for(Set<Integer> set:list) {
                for(Integer i:set) {
                    if(i == wide) continue;
                    if(map.get(i)!=null) {                    
                        map.put(i,map.get(i)+1);                    
                    }else {
                        map.put(i, 1);
                    }
                    max = Math.max(max,map.get(i));
                }
            }
            return min-max;
        }
    }
    

    思路还是挺清楚的,第一步获取所有的墙缝。第二步最大值是所有层数(一个缝没有)。第三步减去缝最多的那一层的缝。反正整体思路就这样,ac了,优化点感觉不少。我直接去看性能排行第一的代码吧,懒得自己优化了:

    class Solution {
        public int leastBricks(List<List<Integer>> wall) {
            int n=wall.size();
            if(n==0||n==1&&wall.get(0).size()>1) return 0;
            //统计有效节点数目(没有去重)
            int count=0;
            for(int i=0;i<n;i++){
                count+=wall.get(i).size()-1;
            }
            if(count==0) return n;
            //计算节点位置,入栈
            int[] points=new int[count];
            int p=-1;
            for(List<Integer> list:wall){
                int listSize=list.size();
                if(listSize==1) continue;
                points[++p]=list.get(0);
                for(int i=1;i<listSize-1;i++){
                    points[++p]=list.get(i);
                    points[p]+=points[p-1];
                }
            }
            //排序
            Arrays.sort(points);
            //统计相同节点最大数目
            int max=-1;
            int bucket=points[0]; int num=0;
            for(int e:points){
                if(e==bucket) num++;
                else{
                    max=Math.max(max,num);
                    bucket=e;
                    num=1;
                }
            }
            max=Math.max(max,num);
            return n-max;
        }
    }
    

    这个怎么说呢,感觉思路是差不多的,找到墙缝最多的那一层。不过实现起来比我的要优雅一些。各种数组操作比我set各种map判断应该是好的。算是学到了一些细节处理的东西。下一题吧。

    下一个更大元素3

    题目:给定一个32位正整数 n,你需要找到最小的32位整数,其与 n 中存在的位数完全相同,并且其值大于n。如果不存在这样的32位整数,则返回-1。

    示例 1:
    输入: 12
    输出: 21
    示例 2:
    输入: 21
    输出: -1

    思路:这个题怎么说呢,我觉得还好吧。挺好理解的,应该也挺好做的。首先比这个大,而且位数相同。就是这个几个数字的重排序。如何只大一点?我感觉是首先列出所有的数字。从后往前遍历。遇到小于后面的元素则替换成稍微大一点的那个。然后后面的正序填充。反正是有思路但是说不出来,我去直接试试代码。
    第一版本的代码ac了,我先贴代码:

    class Solution {
        public int nextGreaterElement(int n) {
            if(n<10) return -1;
            char[] num = String.valueOf(n).toCharArray();
            List<Character> list = new ArrayList<Character>();
            list.add(num[num.length-1]);
            for(int i = num.length-2;i>=0;i--) {
                if(num[i]<num[i+1]) {
                    //肯定是有降序才能值更大。
                    Collections.sort(list);
                    for(int j = 0;j<list.size();j++) {
                        if(list.get(j)>num[i]) {
                            char temp = num[i];
                            num[i] = list.get(j);
                            list.remove(j);
                            list.add(j, temp);
                            for(int k = 0;k<list.size();k++) {
                                num[++i] = list.get(k);
                            }
                            Long res = Long.valueOf(new String(num));
                            return res>Integer.MAX_VALUE?-1:Integer.valueOf(new String(num));
                        }
                    }
                }else {
                    list.add(num[i]);
                }
            }
            return -1;
        }
    }
    

    其实这个题不算难,我上面的代码就是找到第一个较小值,换成较大的,然後后面从小到大依次排列。执行时间是1ms,我觉得可能是细节处理上有一些问题。我去试试。算了,我直接附上性能排行第一的代码:

    class Solution {
        public int nextGreaterElement(int n) {
            char[] arr = String.valueOf(n).toCharArray();
            int i = arr.length  - 2 ;
            while(i >= 0 && arr[i] >= arr[i+1]){
                i -- ;
            }
            if(i < 0) return -1 ;
    
            int j = arr.length -1 ;
            while(j > i && arr[i] >= arr[j]){
                j -- ;
            }
            swap(arr, i , j);
            reverse(arr,i+1);
            try{
                return Integer.parseInt(new String(arr));
            }catch(Exception e){
                return -1 ;
            }
            
        }
    
        public void swap(char[] arr , int i , int j){
            char a = arr[i];
            arr[i] = arr[j];
            arr[j] = a;
        }
        public void reverse(char[] arr , int i){
            int r = arr.length -1;
            while(i < r){
                swap(arr,i++,r--);
            }
        }
    }
    

    其实代码不难,思路也不是理解不了,就是一开始没想到而已。这个题我都不知道说什么好了,所以直接过吧。下一题。

    和为k的子数组

    题目:给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数。

    示例 1 :
    输入:nums = [1,1,1], k = 2
    输出: 2 , [1,1] 与 [1,1] 为两种不同的情况。
    说明 :
    数组的长度为 [1, 20,000]。
    数组中元素的范围是 [-1000, 1000] ,且整数 k 的范围是 [-1e7, 1e7]。

    思路:感觉是滑窗吧。毕竟要连续的子数组。双指针一个在前一个在后。和小于k后面的指针往后走。和大于k前面的指针往后走。反正思路还算清晰。但是这个题有个很大的问题,那就是这个元素可能是负的。这就让滑窗变得复杂了。我再想想。
    算了,暂时没有什么好思路,我先暴力破解一下试试:
    暴力法一次ac了。。虽然是低空掠过。。我附上代码:

    class Solution {
        public int subarraySum(int[] nums, int k) {
            int res = 0;
            for(int i = 0;i<nums.length;i++){
                int sum = nums[i];
                if(sum == k) res++;
                for(int j = i+1;j<nums.length;j++) {                
                    sum += nums[j];
                    if(sum == k) res++;
                }
            }
            return res;
        }
    }
    

    我去看看性能排行第一的代码:

    class Solution {
        public int subarraySum(int[] nums, int k) {
            int[] preSum = new int[nums.length];
            preSum[0] = nums[0];
            for (int i = 1; i < nums.length; i++) {
                preSum[i] = preSum[i - 1] + nums[i];
            }
            HashMap<Integer, Integer> map = new HashMap<>();
            map.put(0, 1);
            int ans = 0;
            for (int i = 0; i < nums.length; i++) {
                Integer cnt = null;
                if ((cnt = map.get(preSum[i] - k)) != null) {
                    ans += cnt;
                }
                if ((cnt = map.get(preSum[i])) != null) {
                    map.put(preSum[i], cnt + 1);
                } else {
                    map.put(preSum[i], 1);
                }
            }
            return ans;
        }
    }
    

    !!!!这个思路我居然没想到!这几天没怎么做题感觉脑子都生锈了。这种方式我以前用过。如果一系列操作以后和操作之前的差值是k。说明这写数字可以凑成k。很简单的一个思路但是我偏偏没想到,太生气了。只要懂了就能明白的思路,我就不多说了,这个题就这样了。
    本篇笔记就记到这里,如果稍微帮到你了记得点个喜欢点个关注。也祝大家工作顺顺利利!

    相关文章

      网友评论

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

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