月度归档: 2021年10月

LeetCode刷题【64】最小路径和

给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。

说明:每次只能向下或者向右移动一步。

 

示例 1:

输入:grid = [[1,3,1],[1,5,1],[4,2,1]]
输出:7
解释:因为路径 1→3→1→1→1 的总和最小。

示例 2:

输入:grid = [[1,2,3],[4,5,6]]
输出:12

 

提示:

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 200
  • 0 <= grid[i][j] <= 100
Related Topics
  • 数组
  • 动态规划
  • 矩阵

  • 👍 1029
  • 👎 0
  • 太简单了,边界情况额外处理下,剩下就是中间的咵咵咵的

    class Solution {
        public int minPathSum(int[][] grid) {
            for (int i = 1; i < grid.length || i < grid[0].length; i++) {
                if (i < grid.length){
                    grid[i][0] += grid[i-1][0];
                }
                if (i < grid[0].length){
                    grid[0][i] += grid[0][i-1];
                }
            }
    
            for (int row = 1; row < grid.length; row++) {
                for (int col = 1; col < grid[row].length; col++) {
                    grid[row][col] = Math.min( grid[row-1][col],grid[row][col-1]) + grid[row][col];
                }
            }
            return grid[grid.length-1][grid[0].length-1];
        }
    }

    LeetCode刷题【38】外观数列

    给定一个正整数 n ,输出外观数列的第 n 项。

    「外观数列」是一个整数序列,从数字 1 开始,序列中的每一项都是对前一项的描述。

    你可以将其视作是由递归公式定义的数字字符串序列:

    • countAndSay(1) = "1"
    • countAndSay(n) 是对 countAndSay(n-1) 的描述,然后转换成另一个数字字符串。

    前五项如下:

    1.     1
    2.     11
    3.     21
    4.     1211
    5.     111221
    第一项是数字 1 
    描述前一项,这个数是 1 即 “ 一 个 1 ”,记作 "11"
    描述前一项,这个数是 11 即 “ 二 个 1 ” ,记作 "21"
    描述前一项,这个数是 21 即 “ 一 个 2 + 一 个 1 ” ,记作 "1211"
    描述前一项,这个数是 1211 即 “ 一 个 1 + 一 个 2 + 二 个 1 ” ,记作 "111221"
    

    描述 一个数字字符串,首先要将字符串分割为 最小 数量的组,每个组都由连续的最多 相同字符 组成。然后对于每个组,先描述字符的数量,然后描述字符,形成一个描述组。要将描述转换为数字字符串,先将每组中的字符数量用数字替换,再将所有描述组连接起来。

    例如,数字字符串 "3322251" 的描述如下图:

     

    示例 1:

    输入:n = 1
    输出:"1"
    解释:这是一个基本样例。
    

    示例 2:

    输入:n = 4
    输出:"1211"
    解释:
    countAndSay(1) = "1"
    countAndSay(2) = 读 "1" = 一 个 1 = "11"
    countAndSay(3) = 读 "11" = 二 个 1 = "21"
    countAndSay(4) = 读 "21" = 一 个 2 + 一 个 1 = "12" + "11" = "1211"
    

     

    提示:

    • 1 <= n <= 30
    Related Topics
  • 字符串

  • 👍 765
  • 👎 0
  • 字符串模拟,这种阅读起来好像还是比直接sb拼接费劲不少。岂止费劲,还看得眼花。
    逻辑也是一样的,

    • 从’1’开始
    • 2个’1′ 就是 “21”
    • 然后“21”就是 1个’2’、1个’1’即“1211”
    • “1211”就是1个’1’、1个’2’、2个’1’,即“111221”
    • 后面依次类推

    class Solution {
        public String countAndSay(int n) {
            if (n == 1){
                return "1";
            }
            char[][] arr = new char[2][4500];
            arr[0][0] = '1';
            int i = 2;
            int cur = 1;
            int pre = 0;
            int preIdx = 0;
            int curIdx = 0;
            while (i <= n){
                cur = (i-1) % 2;
                pre = i % 2;
                preIdx = 0;
                curIdx = -1;
                while (arr[pre][preIdx] != '\u0000'){
                    char numChar = arr[pre][preIdx];
                    int count = 1;
                    while (arr[pre][preIdx] == arr[pre][preIdx+1]){
                        count++;
                        preIdx++;
                    }
                    preIdx++;
                    if (count > 10){
                        int countStringSize = 1;
                        int x = 10;
                        while (count/x != 0){
                            x *= 10;
                            countStringSize++;
                        }
                        curIdx++;
                        while (--countStringSize>=0){
                            arr[cur][curIdx + countStringSize] = (char)('0'+ count%10);
                            count = count / 10;
                        }
                        curIdx += countStringSize-1;
                    }else{
                        arr[cur][++curIdx] = (char)('0' + count);
                    }
                    arr[cur][++curIdx] = numChar;
                }
                i++;
            }
            char[] strChars = new char[curIdx+1];
            System.arraycopy(arr[cur],0,strChars,0,curIdx+1);
            return new String(strChars);
        }
    }

    LeetCode刷题【剑指 Offer II 069】山峰数组的顶部

    符合下列属性的数组 arr 称为 山峰数组山脉数组)

    • arr.length >= 3
    • 存在 i0 < i < arr.length - 1)使得:
      • arr[0] < arr[1] < ... arr[i-1] < arr[i]
      • arr[i] > arr[i+1] > ... > arr[arr.length - 1]

    给定由整数组成的山峰数组 arr ,返回任何满足 arr[0] < arr[1] < ... arr[i - 1] < arr[i] > arr[i + 1] > ... > arr[arr.length - 1] 的下标 i ,即山峰顶部。

     

    示例 1:

    输入:arr = [0,1,0]
    输出:1
    

    示例 2:

    输入:arr = [1,3,5,4,2]
    输出:2
    

    示例 3:

    输入:arr = [0,10,5,2]
    输出:1
    

    示例 4:

    输入:arr = [3,4,5,1]
    输出:2
    

    示例 5:

    输入:arr = [24,69,100,99,79,78,67,36,26,19]
    输出:2
    

     

    提示:

    • 3 <= arr.length <= 104
    • 0 <= arr[i] <= 106
    • 题目数据保证 arr 是一个山脉数组

     

    进阶:很容易想到时间复杂度 O(n) 的解决方案,你可以设计一个 O(log(n)) 的解决方案吗?

     

    注意:本题与主站 852 题相同:https://leetcode-cn.com/problems/peak-index-in-a-mountain-array/

    Related Topics
  • 数组
  • 二分查找

  • 👍 15
  • 👎 0
  • 一般选手

    嗯,找峰值。
    先排除几个特殊情况

    • 数组长度只有1,直接返回0
    • 数组长度只有2,要么0下标的大要么1下标的大
    • 整个数组都是递增的,或者整个数组都是递减的,判断前两位或者后两位的大小关系,这个情况可以包含前面的情况2
    • 剩下的就是在数组中间一定有峰值的,且数组长度一定是大于2的情况,用二分法尝试寻找
      • 找到二分中间值,如果这个比左边和右边 都大,直接返回下标
      • 如果中间值和前面后面组成是递增的,则在说明峰值在后面,继续在后面的区间寻找
      • 如果中间值和前面后面组成的是递减的,则说明峰值在前面,则继续在前面的区间寻找
      • 因为给定的数组肯定只有一个峰值,所以除了上面的三种情况之外没有其他的情况了

    有兴趣的话可以看下另外一题162. 寻找峰值,很像,但是又有点不一样,这题给的数组中可能存在多个峰值。不过只要求返回一个峰值,本质上讨论的情况还是基本一样的。

    关于二分的模板和边界情况的需要多加注意

    class Solution {
        public int peakIndexInMountainArray(int[] arr) {
            if (arr.length == 1){
                return 0;
            }
            if (arr[0] > arr[1]){
                return 0;
            }
            if (arr[arr.length-1] > arr[arr.length-2]){
                return arr.length;
            }
            int left = 0;
            int right = arr.length - 1;
            while (left < right){
                int mid = left + ( (right - left) >> 1 );
                if (arr[mid - 1] < arr[mid] &&  arr[mid + 1] < arr[mid]){
                    return mid;
                }
                if (arr[mid - 1] < arr[mid] &&  arr[mid + 1] > arr[mid]){
                    //上升
                    left = mid+1;
                }else{
                    //下降
                    right = mid;
                }
            }
            return left;
        }
    }
    

    不一般选手

    题面中提供了以下信息

     3 <= arr.length <= 10⁴ 
     0 <= arr[i] <= 10⁶ 
     题目数据保证 arr 是一个山脉数组 

    嗯,才这么点数据,看了下 34 个测试用例,这不简单么?直接撸一下

    class Solution {
        public int peakIndexInMountainArray(int[] arr) {
            int i = -1;
            for (; ++i < arr.length && arr[i] < arr[i+1];);
            return i;
        }
    }

    LeetCode刷题【63】不同路径 II

    一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。

    机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。

    现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?

    网格中的障碍物和空位置分别用 10 来表示。

     

    示例 1:

    输入:obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]]
    输出:2
    解释:
    3x3 网格的正中间有一个障碍物。
    从左上角到右下角一共有 2 条不同的路径:
    1. 向右 -> 向右 -> 向下 -> 向下
    2. 向下 -> 向下 -> 向右 -> 向右
    

    示例 2:

    输入:obstacleGrid = [[0,1],[0,0]]
    输出:1
    

     

    提示:

    • m == obstacleGrid.length
    • n == obstacleGrid[i].length
    • 1 <= m, n <= 100
    • obstacleGrid[i][j]01
    Related Topics
  • 数组
  • 动态规划
  • 矩阵

  • 👍 642
  • 👎 0
  • 同上一题LeetCode刷题【62】不同路径

    之前没石头,那么如果加了石头之后呢,我们用0表示,说明到达这个格子的方法有0种

    那么路径就变成了这样

    class Solution {
        public int uniquePathsWithObstacles(int[][] obstacleGrid) {
    
            if (obstacleGrid[0][0] == 1){
                return 0;
            }
    
            int rowCount = obstacleGrid.length;
            int colCount = obstacleGrid[0].length;
            int[][] dp = new int[rowCount][colCount];
            for (int row = 0; row < rowCount; row++) {
                for (int col = 0; col < colCount; col++) {
                    if (col==0 && row == 0){
                        dp[row][col] = 1;
                        continue;
                    }
                    int left = col > 0 ? dp[row][col-1] : 0;
                    int up = row > 0 ? dp[row-1][col] : 0;
                    dp[row][col] = obstacleGrid[row][col] == 1 ? 0 : ( left + up );
                }
            }
            return dp[rowCount-1][colCount-1];
        }
    }

    LeetCode刷题【62】不同路径

    一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。

    机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。

    问总共有多少条不同的路径?

     

    示例 1:

    输入:m = 3, n = 7
    输出:28

    示例 2:

    输入:m = 3, n = 2
    输出:3
    解释:
    从左上角开始,总共有 3 条路径可以到达右下角。
    1. 向右 -> 向下 -> 向下
    2. 向下 -> 向下 -> 向右
    3. 向下 -> 向右 -> 向下
    

    示例 3:

    输入:m = 7, n = 3
    输出:28
    

    示例 4:

    输入:m = 3, n = 3
    输出:6

     

    提示:

    • 1 <= m, n <= 100
    • 题目数据保证答案小于等于 2 * 109
    Related Topics
  • 数学
  • 动态规划
  • 组合数学

  • 👍 1141
  • 👎 0
  • 机器人行走路径只能向右或者向下。那么每个到达每个格子的上一步可能是左边往右过来的,或者是上面一个格子下来的

    边界情况,横向第0行或者纵向第一列只有一种可能性。你可以选择单独初始化或者外面部分的值取0。

    class Solution {
        public int uniquePaths(int m, int n) {
            int[][] dp = new int[m][n];
            Arrays.fill(dp[0],1);
            for (int row = 1; row < m; row++) {
                dp[row][0] = 1;
                for (int col = 1; col < n; col++) {
                    dp[row][col] = dp[row][col-1] + dp[row-1][col];
                }
            }
            return dp[m-1][n-1];
        }
    }
    

    当然,如果你把这个图右旋45°的话,你会看到这样一幅图

    是不是非常眼熟?对他就是LeetCode刷题【118】杨辉三角LeetCode刷题【119】杨辉三角 II

    LeetCode刷题【304】二维区域和检索 – 矩阵不可变

    给定一个二维矩阵 matrix以下类型的多个请求:

    • 计算其子矩形范围内元素的总和,该子矩阵的 左上角(row1, col1)右下角(row2, col2)

    实现 NumMatrix 类:

    • NumMatrix(int[][] matrix) 给定整数矩阵 matrix 进行初始化
    • int sumRegion(int row1, int col1, int row2, int col2) 返回 左上角 (row1, col1) 、右下角 (row2, col2) 所描述的子矩阵的元素 总和

     

    示例 1:

    输入: 
    ["NumMatrix","sumRegion","sumRegion","sumRegion"]
    [[[[3,0,1,4,2],[5,6,3,2,1],[1,2,0,1,5],[4,1,0,1,7],[1,0,3,0,5]]],[2,1,4,3],[1,1,2,2],[1,2,2,4]]
    输出: 
    [null, 8, 11, 12]
    
    解释:
    NumMatrix numMatrix = new NumMatrix([[3,0,1,4,2],[5,6,3,2,1],[1,2,0,1,5],[4,1,0,1,7],[1,0,3,0,5]]]);
    numMatrix.sumRegion(2, 1, 4, 3); // return 8 (红色矩形框的元素总和)
    numMatrix.sumRegion(1, 1, 2, 2); // return 11 (绿色矩形框的元素总和)
    numMatrix.sumRegion(1, 2, 2, 4); // return 12 (蓝色矩形框的元素总和)
    

     

    提示:

    • m == matrix.length
    • n == matrix[i].length
    • 1 <= m, n <= 200
    • -105 <= matrix[i][j] <= 105
    • 0 <= row1 <= row2 < m
    • 0 <= col1 <= col2 < n
    • 最多调用 104 次 sumRegion 方法
    Related Topics
  • 设计
  • 数组
  • 矩阵
  • 前缀和

  • 👍 307
  • 👎 0
  • 和另一个题目LeetCode刷题【1314】矩阵区域和基本一毛一样

    同样的都是矩阵数据,都是求中间某个矩形部分的内的所有数字总和

    求这里的红色区域内的数字总和的话,就是

    红色区域的数字之和 = 蓝色区域的数字之和 – 黄色区域之和 – 紫色区域之和 + 黑色区域之和

    黑色区域的和值之前重复减掉的

    而求各个格子对应的左上区域的前缀和的方法

    29的蓝色框线区域内的所有数字之和 = 23格子代表的紫色框线区域的所有数字之和 + 28代表的黄色框线区域数字之和 – 22代表的黑色框线区域的所有数字之和 + 29这个格子内的值

    class NumMatrix {
    
        int[][] preSum;
    
        public NumMatrix(int[][] matrix) {
            preSum = new int[matrix.length][matrix[0].length];
            for (int row = 0; row < preSum.length; row++) {
                for (int col = 0; col < preSum[row].length; col++) {
                    preSum[row][col] = getMatrixNum( preSum, row-1, col)
                            + getMatrixNum( preSum, row, col-1)
                            - getMatrixNum( preSum, row-1, col-1)
                            + matrix[row][col];
                }
            }
        }
        
        public int sumRegion(int row1, int col1, int row2, int col2) {
            int sum = getMatrixNum(preSum, row2, col2)
                    + getMatrixNum(preSum, row1-1, col1-1)
                    - getMatrixNum(preSum, row2, col1-1)
                    - getMatrixNum(preSum, row1-1, col2);
            return sum;
        }
    
        public int getMatrixNum(int[][] matrix, int row, int col){
            if (row < 0 || col < 0){
                return 0;
            }
            return matrix[row][col];
        }
    }

    LeetCode刷题【1314】矩阵区域和

    给你一个 m x n 的矩阵 mat 和一个整数 k ,请你返回一个矩阵 answer ,其中每个 answer[i][j] 是所有满足下述条件的元素 mat[r][c] 的和: 

    • i - k <= r <= i + k,
    • j - k <= c <= j + k
    • (r, c) 在矩阵内。

     

    示例 1:

    输入:mat = [[1,2,3],[4,5,6],[7,8,9]], k = 1
    输出:[[12,21,16],[27,45,33],[24,39,28]]
    

    示例 2:

    输入:mat = [[1,2,3],[4,5,6],[7,8,9]], k = 2
    输出:[[45,45,45],[45,45,45],[45,45,45]]
    

     

    提示:

    • m == mat.length
    • n == mat[i].length
    • 1 <= m, n, k <= 100
    • 1 <= mat[i][j] <= 100
    Related Topics
  • 数组
  • 矩阵
  • 前缀和

  • 👍 105
  • 👎 0
  • 理解题意

    根据题意:所求新数组answer[row][col]的值就是原数组的mat[row][col]位置上下左右下标距离k这个这个区域内的所有数字之和

    若距离k为1,则answer[3][3]的值为周围距离k的矩形区域内的所有数字之和。
    那么顺着题意,想要求这片红色区域的所有数字只有的话,我们有两个方法

    遍历这个区域内的所有格子,求和处理
    这片红色区域的数字之和 = 蓝色区域的数字之和 - 黄色区域之和 - 紫色区域之和 + 黑色区域之和
    黑色区域的和值之前重复减掉的那么顺着这个和数组的前缀和非常像的思路,我们要怎么求这某个格子的所有左上格子内的数字之和呢?

    右下角为29的蓝色区域的所有数字之和 = 23格子代表的紫色区域的所有数字之和 + 28代表的黄色区域数字之和 - 22代表的黑色区域的所有数字之和 + 29这个格子内的值

    因为遍历的顺序是从上往下,从左往右,在第29这个格子的时候,前面的22,23,28的格子内的值都是已知的。

    那么思路就很清楚了

    一些边界情况

    1. 在求前缀和的时候,如果是第0行或者是每行的第一个格子,他们的上一行或者前一个格子是不存在的,直接取值0
    2. 当在求第一个图中红色区域的时候,如果红色区域超出了原本的数组的边界,应当取原本数组边界的位置

    代码

    class Solution {
        public int[][] matrixBlockSum(int[][] mat, int k) {
            int[][] preSum = new int[mat.length][mat[0].length];
            for (int row = 0; row < preSum.length; row++) {
                for (int col = 0; col < preSum[row].length; col++) {
                    preSum[row][col] = getMatrixNum( preSum, row-1, col)
                            + getMatrixNum( preSum, row, col-1)
                            - getMatrixNum( preSum, row-1, col-1)
                            + mat[row][col];
                }
            }
    
            int[][] answer = new int[mat.length][mat[0].length];
            for (int row = 0; row < answer.length; row++) {
                for (int col = 0; col < answer[row].length; col++) {
                    answer[row][col] = getPreSumMatrixNum(preSum, row+k, col+k)
                            + getPreSumMatrixNum(preSum, row-k-1, col-k-1)
                            - getPreSumMatrixNum(preSum, row+k, col-k-1)
                            - getPreSumMatrixNum(preSum, row-k-1, col+k);
                }
            }
            return answer;
        }
    
        public int getMatrixNum(int[][] matrix, int row, int col){
            if (row < 0 || col < 0){
                return 0;
            }
            return matrix[row][col];
        }
    
        public int getPreSumMatrixNum(int[][] matrix, int row, int col){
            if (row < 0 || col < 0){
                return 0;
            }
            row = row > matrix.length-1 ? matrix.length-1 : row;
            col = col > matrix[row].length-1 ? matrix[row].length-1 : col;
            return matrix[row][col];
        }
    
    }
    

    本题和另外一题,LeetCode刷题【304】二维区域和检索 – 矩阵不可变基本一样,可以结合起来一起理解