力扣刷题笔记-动态规划

1 篇文章 0 订阅
1 篇文章 0 订阅

力扣刷题笔记-动态规划

此笔记为力扣动态规划专项练习题笔记,将随着做题持续更新。
题目地址:动态规划(基础版)

1.爬楼梯问题

问题描述:

在这里插入图片描述

解题思路:

我们把前几项都列出来,可以看到,此题目有一定的规律:

1层 1种方式 一步爬上去
2层 2种方式 1步1步或者2步直接上去
3层 3种方式 1步1步1步、1步2步、2步1步
4层 5种方式 1步1步1步1步、1步1步2步、1步2步1步、2步1步1步、2步2步
。。。

可以看到当共有n层楼时,那么根据规律,爬n层楼的方式共有:
f(n) = f(n-2) + f(n-1)
事实的确如此,因为在爬到第n层时,我们有两种办法到达第n层,分别是:

1层台阶到达第n层,对应的是之前到达n-1层,共有f(n-1)种方式
2层台阶到达第n层,对应的是之前到达n-2层,共有f(n-2)种方式

所以,到达第n层有 f(n-2) + f(n-1)种方式。

代码实现:

面对这样的问题,首先想到的就是递归,代码如下:

class Solution {
    public:
	    int climbStairs(int n) {
	        if (n == 1) {
	            return 1;
	        }
	        if (n == 2) {
	            return 2;
	        }
	
	        return climbStairs(n-1) + climbStairs(n-2);
	    }
};

时间复杂度: O(2^n) ,每一层递归的复杂度是上一层递归的2倍。
空间复杂度: O(n) , n是递归的层次。
在力扣的简单题中,这显然是不可能通过的,所以去看了其他大佬的解题思路,此处引用大佬的作图:

作者:Caddy
链接:https://leetcode.cn/problems/climbing-stairs/solutions/846147/yuan-lai-hui-pa-lou-ti-de-zheng-que-zi-s-pjez/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

以下图中计算5为例, 递归的层次是5层,顶层是1,没深入一层,算法的复杂度乘以2。
在这里插入图片描述
分析发现,递归解法中存在重复计算(比如下图中标注的),那么我们可以减少重复计算,将计算的结果保存起来,再次有相关计算的时候,直接从缓存中读取。
在这里插入图片描述
此时代码如下:

class Solution{
	public:
	    unordered_map<int,int> mp;
	    int climbStairs(int n) {
	        if(n == 1) return 1;
	        if(n == 2) return 2;
	
	        auto it = mp.find(n);
	        if( it != mp.end() )
	            return it->second;
	
	        int sum = climbStairs(n-1) + climbStairs(n-2);
	        mp.insert(pair<int, int> (n, sum));
	        return sum;
	    }
};

此时虽然我们通过了力扣的提交,但在日常工作中,递归还是一种非常不可取的办法,在很多公司内部也是明确禁止使用递归的,因此决定进一步优化,使用递推来解决此问题:

class Solution {
    public:
	    int climbStairs(int n) {
	        if (n == 1) {
	            return 1;
	        }
	        if (n == 2) {
	            return 2;
	        }
	
	        int *dp = new int[n];
	        dp[0] = 1;
	        dp[1] = 2;
	        
	        for (int i=2; i<n; i++){
	        	dp[i] = dp[i-2] + dp[i-1];
	        }
	        int num = dp[n-1];
	        delete[] dp;
	        return num;
	    }
};

此时时间复杂度为O(n),空间复杂度为n,进一步检查发现,数组dp只是在计算过程中的运用,实际每次循环过程中只用到了3个单元,所以进行再次优化:

class Solution {
    public:
	    int climbStairs(int n) {
	        if (n == 1) {
	            return 1;
	        }
	        if (n == 2) {
	            return 2;
	        }
	
	        int x1 = 1, x2 = 2, x= 0;
	        
	        for (int i=2; i<n; i++){
	        	x = x1 + x2;
	        	x1 = x2;
	        	x2 = x;
	        }
	        return x;
	    }
};

此时代码的时间复杂度是O(n),空间复杂度是O(1)。

2.斐波那契数列

问题描述:

在这里插入图片描述

解题思路:

经过分析,可以看出与上面的爬楼问题解题思路基本一致。

代码实现

class Solution {
	public:
	    int fib(int n) {
	        if (n < 2) return n;
	
	        int x1 = 0;
	        int x2 = 1;
	        int x = 0;
	
	        for (int i=1; i<n; i++){
	            x = x1 + x2;
	            x1 = x2;
	            x2 = x;
	        }
	
	        return x;
	    }
};

3.第N个泰波纳契数

问题描述:

在这里插入图片描述

解题思路

与上面的爬楼梯问题和斐波那契数列问题一样,不过这次换成了前三个数的和。

代码实现

class Solution {
    public:
        int tribonacci(int n) {
            if (n==0) return 0;
            if (n==1) return 1;
            if (n==2) return 1;

            int x1 = 0, x2 = 1, x3 = 1, x;

            for (int i=2; i< n; i++){
                x = x1 + x2 + x3;
                x1 = x2;
                x2 = x3;
                x3 = x;
            }
            
            return x;
        }
};

4.使用最小花费爬楼梯

问题描述:

在这里插入图片描述

解题思路:

假设数组cost的长度为n,那么cost[0]——cost[n-1]表示的就是这n级台阶,每一级对应的价格。到达的最后一个楼梯则对应n。那么创建一个数组,db[n+1],表示到达每一级台阶所花费的最小金额。
因为可以选择下标0或者下标1作为初始阶梯,那么到达下标0或者下标1的最小花费应该都是0,即:dp[0] = dp[1] = 0;
此时如果n=2,那么登上第2级阶梯的总花费应该是登上第0级阶梯的最小总花费 +从第0级走两步登上第2级阶梯登上第1级阶梯的最小总花费 + 从第1级走一步登上第2级阶梯中的花费较小的一个。
扩展到第n级台阶,就是:
dp[n] = min(dp[n-2] + cost[n-2], dp[n-1] + cost[n-1])
其中:
dp[n-x]:表示到第n-x级台阶的最小总花费,x=1或2。
cost[n-x]: 表示从第n-x级台阶移动所需的花费,x=1或2。
此时得到的dp[n]就是到达第n级台阶时的最小花费。

代码实现:

class Solution {
public:
    int minCostClimbingStairs(vector<int>& cost) {
        int n = cost.size();
          
        vector<int> dp(n+1);
        dp[0] = dp[1] = 0;

        for (int i=2; i<=n; i++){
            dp[i] = min(dp[i-1] + cost[i-1], dp[i-2] + cost[i-2]);
        }

        return dp[n];
    }
};

此时算法的时间复杂度为O(n),空间复杂度也是O(n),使用滚动数组思想对其进行优化(此处只是借鉴了思想,并没有真的使用滚数组)。

class Solution {
public:
    int minCostClimbingStairs(vector<int>& cost) {
        int n = cost.size();
        int x1 = 0, x2 = 0, x;

        for (int i=1; i<n; i++){
            x = min(x1+cost[i-1], x2+cost[i]);
            x1 = x2;
            x2 = x;
        }

        return x;
    }
};

代码中x1表示到达n-2级台阶时的最小总花费,x2表示到达n-1级台阶时的最小总花费,x表示到达n级台阶时的最小总花费。
此时算法的时间复杂度为O(n),空间复杂度为O(1)。

5. 打家劫舍

问题描述:

在这里插入图片描述

解题思路:

假设只有一间房间,那么你能偷取的最大金额就是room[0]中的金额。
如果有两间房间,题目规定不能偷相邻房间,此时你能偷取的最大金额就是max(room[0], room[1])
如果有三间房间,情况就变成了偷取room[0]和room[2]偷取room[1]中最大的那个。
四间房间就是,偷取前两间房中最大的情况 + 第四间房中的金额(即不偷第三间房屋)偷取前三间房屋最大金额中最大的那个。
可总结出:
f(n) = max(f(n-2)+room[n], f(n-1))

代码实现:

class Solution {
public:
    int rob(vector<int>& nums) {
        int n = nums.size();
        if (n==1) return nums[0];
        if (n==2) return max(nums[0], nums[1]);
        
        int x1 = nums[0], x2=max(nums[0], nums[1]), x = 0;

        for (int i = 2; i<n; i++){
            x = max(x1 + nums[i], x2);
            x1 = x2;
            x2 = x;
        }

        return x;
        
    }
};

其中x1表示前n-2个房屋能偷到的最大金额, x2表示前n-1个房屋能偷到的最大金额。
此算法时间复杂度O(n),空间复杂度O(1)。

6. 删除并获得点数

问题描述:

在这里插入图片描述

解题思路:

刚开始看到这个题,我也很困惑,仔细思考一下,这道题相当于是在前面打家劫舍的题目上再次套了一层壳子,即房屋排列无序,并且偷盗相同金额的房屋的钱不会触发报警。所以有了如下解题思路:

  1. 找出数组nums中的最大值,maxVal
  2. 创建一个新数组sum,用于存储0--maxVal的所有值,并使下标与数值对应,此时sum的长度应该为maxVal+1
  3. 循环遍历nums数组,并将其值,累加到对应的sum数组下标中,此处相当于构建了一个hash表,没有值对应的下标内的值为0
for (int val: nums) {
    sum[val] = sum[val] + val;
}

假设有nums数组:

nums[3] = [3, 4, 2, 3];

则获取的sum数组为:

// 因为 nums 中的最大值为4,为了在后续计算时能够访问到sum[4],所以sum的长度为5
sum[5] = [0, 0, 2, 6, 4];
  1. 此时就转变为在sum数组中,相邻的下标不可同时获取,问最大能够累计多少分值的问题,就演变成上面的打家劫舍问题。

代码实现:

/**
现在有一个数组nums,从nums数组中取一个数,值为x,则数组中值为x-1和x+1的都不能再次选择,并且获得积分x
*/
class Solution {
public:
    int deleteAndEarn(vector<int>& nums) {
        // 创造一个数组,用于存放可以偷窃的金额
        int maxVal = 0; // nums 数组中的最大值
        for (int val: nums) {
            maxVal = max(maxVal, val);
        }

        vector<int> sum(maxVal+1);

        for (int val: nums) {
            sum[val] = sum[val] + val;
        }
		// 此时的sum数组,就是偷窃金额排序后的数组,对应原房间金额,从小到大排序,没有的金额为0。

        if (maxVal == 0) {
            return sum[0];
        }
        if (maxVal == 1) {
            return sum[1];
        }

        int sum_size = sum.size();

        int x1 = sum[0], x2 = max(sum[0], sum[1]), x;

        for (int i=2; i<sum_size; i++) {
            x = max(x1+sum[i], x2);
            x1 = x2;
            x2 = x;
        }

        return x;

    }
};

此算法时间复杂度为O(n + maxVal),空间复杂度为O(maxVal)。很容易发现,当nums只有一个值,但这个值超级大时,sum数组的前maxVal-1位都是无效值,造成了极大空间浪费。故做如下修改:

class Solution {
private:
    int rob(vector<int> &sum){
        int sum_size = sum.size();
        if (sum_size==1) return sum[0];
        if (sum_size == 2) return max(sum[0], sum[1]);

        int x1 = sum[0], x2 = max(sum[0], sum[1]), x;

        for (int i=2; i<sum_size; i++){
            x = max(x1 + sum[i], x2);
            x1 = x2;
            x2 = x;
        }

        return x;

    }

public:
    int deleteAndEarn(vector<int>& nums) {
        int x = 0;
        sort(nums.begin(), nums.end());
        vector<int> sum = {nums[0]};
        for (int i=1; i<nums.size(); i++) {
            if (nums[i] == nums[i-1]){
                sum.back() = sum.back() + nums[i];
            } else if (nums[i] == nums[i - 1] + 1){
                sum.push_back(nums[i]);
            } else {
                x = x + rob(sum);
                sum = {nums[i]};
            }
        }

        x = x + rob(sum);

        return x;

    }
};

在上述代码中,使用vector<int> sum = {nums[0]};生成的向量替代了原有的数组,这个向量存储的也是相同的数据之和,但其只存储连续的值的和,当碰到不连续的值——即排序后的nums中,nums[i] != nums[i-1] 并且 num[i] > nums[i-1] + 1时便会计算一遍当前sum数组能够获取的最大得分 ,并将原sum中的数据清空,只存储计算结果。需要注意的是,在用于计算的sum数组中,其对应的是连续的nums的值中,相同值的和,而sum中的值不是连续的。可以看下面的例子:

nums[5] = [1, 1, 2, 5, 6]

那么在执行过程中,根据步骤sum的内容变化如下:

sum = nums[0] = [1]
sum[0] = sum[0] + nums[1] = [2]
sum[1] = nums[2] = [2, 2]

// 进行一次计算, 计算结果累加
sum[0] = [5]
sum[1] = [5, 6]

// 进行一次计算, 计算结果累加

其实就是一个分批计算的思想,先将nums数组排序,再循环遍历,发现这段步长为1的连续值,就计算一遍,后面的连续步长不为1,也不会影响前面已经算出的能够获取的最大值。
这种方法的时间复杂度为O(NlogN),其中N为nums数组的长度,对nums数组的排序需要花费O(NlogN),遍历计算需要O(N)的时间,所以总的时间复杂度为故总的时间复杂度为 O(NlogN)。
空间复杂度:O(N)。统计 sum 至多需要花费 O(N) 的空间。

7.不同路径

问题描述:

在这里插入图片描述

解题思路:

通过观察图片,我们可以看到如下规律:
在这里插入图片描述
因为每一格都只能从其左边或者上边的格子到达,所以这一格的到达方法应该是到达左边格子和右边格子的方法数量之和。
即设方阵:M[i, j]
M[i, j] = M[i-1, j] + M[i, j-1]
又因为第一行和第一列都在列表边缘,到达这些地方的方格的办法只能是左边或者上边中的一种,而另一种为0,所以能使1种走法。

代码实现:

class Solution {
public:
    int uniquePaths(int m, int n) {
        vector<vector <int>> A(m, vector<int>(n));

        for (int i=0; i< m; i++) {
            A[i][0] = 1;
        }

        for (int j=0; j<n; j++) {
            A[0][j] = 1;
        }

        for (int i=1; i<m; i++) {
            for (int j=1; j<n; j++) {
                A[i][j] = A[i-1][j] + A[i][j-1];
            }
        }
        return A[m-1][n-1];
    }
};

此时时间复杂度是O(MN),空间复杂度O(MN)。
官方给出了第二种解法:由于 f(i,j) 仅与第 i 行和第 i−1 行的状态有关,因此我们可以使用滚动数组代替代码中的二维数组,使空间复杂度降低为 O(n)。

class Solution {
public:
    int uniquePaths(int m, int n) {
        vector<int> A(n, 1);
        for (int i=1; i<m; i++) {
            for (int j=1; j<n; j++) {
                A[j] += A[j-1];
            }
        }

        return A[n-1];
    }
};

此时算法的空间复杂度为O(n)。

8.最小路径和

问题描述:

在这里插入图片描述

解题思路:

与上面的不同路径大致相同,因为到达第[m,n],只有两种办法从[m-1, n]或者[m, n-1],所以到达第A[m][n]的最小路径,是到达A[m-1][n]A[m][n]中较小的一个加上当前的grid[m][n]。此处的m和n代表的是区间内的任意值。而A[m][n]表示到达[m, n]处的最小路径,grid[m][n]代表[m, n]处的值。且第一行和第一列的A[0][n]A[m][0]只能是A[0][n-1] + grid[0][n]A[m-1][0] + grid[m][0]

代码实现:

class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        if (grid.size() == 0 || grid[0].size() == 0) {
            return 0;
        }

        int m = grid.size();
        int n = grid[0].size();

        vector<vector<int>> A(m, vector<int>(n));

        A[0][0] = grid[0][0];
        
        for (int i=1; i< m; i++) {
            A[i][0] = grid[i][0] + A[i-1][0];
        }

        for (int j=1; j<n; j++) {
            A[0][j] = grid[0][j] + A[0][j-1];
        }

        for (int i=1; i<m; i++) {
            for (int j=1; j<n; j++) {
                A[i][j] = grid[i][j] + min(A[i-1][j], A[i][j-1]);
            }
        }

        return A[m-1][n-1];
    }
};

此时程序的时间复杂度是O(mn),空间复杂度是O(mn)。
使用滚动数组法优化:

class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        if (grid.size() == 0 || grid[0].size() == 0) {
            return 0;
        }

        int m = grid.size();
        int n = grid[0].size();

        vector<int> A(n);

        A[0] = grid[0][0];

        for (int i=1; i<n; i++) {
            A[i] = A[i-1] + grid[0][i]; // 第一行初始化
        }

        for (int i=1; i<m; i++) {
            A[0] = A[0] + grid[i][0]; // 将每行的第一个元素的到达路径最小值求出来
            for (int j=1; j<n; j++) {
                A[j] = grid[i][j] + min(A[j-1], A[j]); // A[j-1]代表的是到达左侧格子的最小路径和,A[j]代表到达上方格子的最小路径和
            }
        }

        return A[n-1];
    }
};

优化后的空间复杂度为O(n)。

9.不同路径Ⅱ

问题描述:

在这里插入图片描述

解题思路:

此题为不同路径Ⅱ,其解题思路与上面的不同路径相同,唯一需要考虑的就是障碍物的位置,如果障碍物在第一行或者第一列,那么小人将永远无法到达此格及其向右(障碍物在第一行)或向下(障碍物在第一列)的所有格。

代码实现:

class Solution {
public:
    int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
        int m = obstacleGrid.size();
        int n = obstacleGrid[0].size();

        vector<vector<int>> A(m, vector<int> (n, 0));

        A[0][0] = (obstacleGrid[0][0] == 0);

        for (int i=0; i<m; i++) {
            for (int j=0; j<n; j++) {
                if (obstacleGrid[i][j] == 1) { // 如果此格是障碍物,那么到达此格的方式有0种
                    A[i][j] = 0;
                    continue;
                } 
                if ((i == 0) && (j-1 >= 0) && (obstacleGrid[i][j-1] == 0)) { // 如果此格在第一行,并且其左侧的格子可到达,则达到此格的方法1
                    A[i][j] = A[i][j] + A[i][j-1];
                    continue;
                }
                if ((i-1 >= 0) && (j == 0) && obstacleGrid[i-1][j] == 0) {  // 如果此格在第一列,并且其上侧的格子可到达,则达到此格的方法1
                    A[i][j] = A[i][j] + A[i-1][j];
                    continue;
                }
                if ((i-1 >= 0) && (j-1 >= 0)) {
                    A[i][j] = A[i-1][j] + A[i][j-1];
                    continue;
                }
                
            }
        }
        return A[m-1][n-1];

    }
};

此程序时间复杂度为O(mn),空间复杂度为O(mn)。进一步也可以使用滚动数组法优化:

class Solution {
public:
    int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
        int m = obstacleGrid.size();
        int n = obstacleGrid[0].size();

        vector<int> A(n);

        A[0] = (obstacleGrid[0][0] == 0);

        for (int i=0; i<m; i++) {
            for (int j=0; j<n; j++) {
                if (obstacleGrid[i][j] == 1) {
                    A[j] = 0;
                    continue;
                } 
                if((j - 1 >= 0) && (obstacleGrid[i][j-1] == 0)) {
                    A[j] = A[j-1] + A[j];
                }
            }
        }

        return A[n-1];
    }
};

此时程序的空间复杂度为O(n)

10.三角形最小路径和

问题描述:

在这里插入图片描述

解题思路:

利用动态规划的思路进行解题,创建一个[n, n](n=triangle.size())的数组,用于存储到达对应于triangle每一个位置的最小路径。与之前方阵的动态规划最小路径有所不同的是,现在在每一行中,到达该行最左侧格子与最右侧格子的方法只有一种,分别是从其正上方的格子到达和从其左上方的格子到达,即:
1
2 3
4 5 6
7 8 9 10

拿第三行举例:
到达4号位置只能够从2号位置到达,到达6号位置只能从3号位置到达,所以边界处需要单独处理,其余位置,例如到达8号位置,可以从4号和5号中选择较小的一个。
最后,只需要找出最后一排中的最小值,即为到达三角形底部的最小路径。

代码实现:

class Solution {
public:
    int minimumTotal(vector<vector<int>>& triangle) {
        int n = triangle.size();

        vector<vector<int>> A(n, vector<int>(n));

        A[0][0] = triangle[0][0];

        for (int i = 1; i < n; i++) {

            A[i][0] = A[i-1][0] + triangle[i][0];  // 对每一行的第一列单独处理
            
            for (int j = 1; j < i; j++) {
                A[i][j] = triangle[i][j] + min(A[i-1][j], A[i-1][j-1]);  // 对中间列进行处理,因为i的下标从0开始,所以i行实际对应的是第(i+1)行,也就有(i+1)列
            }

            A[i][i] = A[i-1][i-1] + triangle[i][i];  // 对最每一行后一列,也就是第i列进行单独处理

        }

        return *min_element(A[n-1].begin(), A[n-1].end());
    }
};

该程序的时间复杂度为O(nxn),其中n为三角形行数,空间复杂度为O(nxn),可以使用滚动数组进行空间优化,将空间复杂度为O(n)
代码如下:

class Solution {
public:
    int minimumTotal(vector<vector<int>>& triangle) {
        int n = triangle.size();

        vector<int> A(n);

        A[0] = triangle[0][0];

        for (int i = 1; i < n; i++) {
            A[i] = A[i-1] + triangle[i][i]; // 先处理每一行的最后一列数据,如果先处理前面的数据将会覆盖掉要使用的(i-1,j-1)处的数据
            for (int j = i-1; j > 0; j--) {
                A[j] = min(A[j-1], A[j]) + triangle[i][j]; // 处理中间数据 A[j]表示的是到达当前格子上方的格子的最小路径和,A[j-1]表示的是到达当前格子左上方的格子的最小路径和。
            }
            A[0] = A[0] + triangle[i][0]; // 最后处理第一列,A[0] + triangle[i][0]中的A[0]表示到达上一行第一列格子的最小路径和
        }

        return *min_element(A.begin(), A.end());
    }
};

进阶思维

在题目的讨论区看到了另一种更加快速便捷的解题思路,由底向顶逆推法,也就是从三角形的倒数第二行开始向上进行计算,找出其到达每一行的最小路径。
代码实现如下:

class Solution {
public:
    int minimumTotal(vector<vector<int>>& triangle) {
        vector<int> dp(triangle.back());
        for(int i = triangle.size() - 2; i >= 0; i --)
            for(int j = 0; j <= i; j ++) {
                dp[j] = min(dp[j], dp[j + 1]) + triangle[i][j];
            }
        return dp[0];
    }
};

此算法的时间复杂度是O(nxn),空间复杂度为O(n) ,当相比于由上向下的思路,它省去了很多的计算步骤。

11.下降路径最小和

问题描述:

在这里插入图片描述

解题思路:

很明显的动态规划问题,到达第一行的所有格子的最小路径与原数组相同,从第一行开始的每一行,都只能从其正上方,或斜上方到达,则对于到达第[n, n]处的最小路径为[n, n] + min([n-1, n-1], [n-1, n], [n-1, n+1]),其中[n-1, n-1], [n-1, n], [n-1, n+1]每个格子都代表到达该位置的最小路径和,[n, n]为原数组处的值,其中第一列和最后一列要单独处理。

代码实现:

class Solution {
public:
    int minFallingPathSum(vector<vector<int>>& matrix) {
        int n = matrix.size();
        vector<vector<int>> A(n, vector<int>(n));
        for(int i = 0; i < n; i++) {
            A[0][i] = matrix[0][i];
        }

        for (int i = 1; i < n; i++) {
            A[i][0] = matrix[i][0] + min(A[i-1][0], A[i-1][1]); // 第一列
            for (int j=1; j<n-1; j++) {
                A[i][j] = matrix[i][j] + min(A[i-1][j-1], min(A[i-1][j], A[i-1][j+1]));
            }
            A[i][n-1] = matrix[i][n-1] + min(A[i-1][n-2], A[i-1][n-1]); // 最后一列
        }
        return  *min_element(A[n-1].begin(), A[n-1].end());
    }
};

此程序空间复杂度为O(nxn),时间复杂度为O(nxn),因为计算完第[n, n]格子时,第[n, n+1]格子的计算依然需要使用[n+1, n],所以不能用常规滚动数据的方式实现,但也可以将数组空间压缩到2行。

class Solution {
public:
    int minFallingPathSum(vector<vector<int>>& matrix) {
        int n = matrix.size();
        vector<vector<int>> A(2, vector<int>(n));
        for(int i = 0; i < n; i++) {
            A[0][i] = matrix[0][i];
        }

        for (int i = 1; i < n; i++) {
            A[1][0] = matrix[i][0] + min(A[0][0], A[0][1]); // 单独处理第一列
            for (int j=1; j<n-1; j++) {
                A[1][j] = matrix[i][j] + min(A[0][j-1], min(A[0][j], A[0][j+1]));
                A[0][j-1] = A[1][j-1];  // 将已经计算好的结果移动到第0行
            }
            A[1][n-1] = matrix[i][n-1] + min(A[0][n-2], A[0][n-1]); // 单独处理最后一列
            A[0][n-2] = A[1][n-2];  // 将已经计算好的倒数第二列结果移动到第0行
            A[0][n-1] = A[1][n-1];  // 将已经计算好的倒数第一列结果移动到第0行
        }
        return  *min_element(A[0].begin(), A[0].end());
    }
};

此时程序的时间复杂度为O(nxn),空间复杂度为O(n)。

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

徐先生没洗头

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值