递归和动态规划都是将原问题拆成多个子问题然后求解,他们之间最本质的区别是,动态规划保存了子问题的解,避免重复计算。
斐波那契数列
1. 爬楼梯
70. Climbing Stairs (Easy)
题目描述:有 N 阶楼梯,每次可以上一阶或者两阶,求有多少种上楼梯的方法。
定义一个数组 dp 存储上楼梯的方法数(为了方便讨论,数组下标从 1 开始),dp[i] 表示走到第 i 个楼梯的方法数目。
第 i 个楼梯可以从第 i-1 和 i-2 个楼梯再走一步到达,走到第 i 个楼梯的方法数为走到第 i-1 和第 i-2 个楼梯的方法数之和。
d p [ i ] = d p [ i − 1 ] + d p [ i − 2 ] dp[i] = dp[i-1] + dp[i-2] dp[i]=dp[i−1]+dp[i−2]
考虑到 dp[i] 只与 dp[i - 1] 和 dp[i - 2] 有关,因此可以只用两个变量来存储 dp[i - 1] 和 dp[i - 2],使得原来的 O(N) 空间复杂度优化为 O(1) 复杂度。
class Solution {
public:
int climbStairs(int n) {
if(n < 2) return n;
vector<int> dp(n);
dp[0] = 1; dp[1] = 2;
for(int i=2; i<n; i++)
dp[i] = dp[i-1] + dp[i-2];
return dp.back();
}
};
2. 强盗抢劫
198. House Robber (Easy)
题目描述:抢劫一排住户,但是不能抢邻近的住户,求最大抢劫量。
定义 dp 数组用来存储最大的抢劫量,其中 dp[i] 表示抢到第 i 个住户时的最大抢劫量。
由于不能抢劫邻近住户,如果抢劫了第 i -1 个住户,那么就不能再抢劫第 i 个住户,所以
d p [ i ] = m a x ( d p [ i − 2 ] + n u m s [ i ] , d p [ i − 1 ] ) dp[i] = max(dp[i-2] + nums[i], dp[i-1]) dp[i]=max(dp[i−2]+nums[i],dp[i−1])
class Solution {
public:
int rob(vector<int>& nums) {
if(nums.size() <= 1)
return nums.empty() ? 0 : nums[0];
vector<int> dp(nums.size(), 0);
dp[0] = nums[0]; dp[1] = max(nums[0], nums[1]);
for(int i=2; i<nums.size(); i++)
dp[i] = max(nums[i]+dp[i-2], dp[i-1]);
return dp[nums.size()-1];
}
};
3. 强盗在环形街区抢劫
213. House Robber II (Medium)
环状排列意味着第一个房子和最后一个房子中只能选择一个偷窃,因此可以把此环状排列房间问题约化为两个单排排列房间子问题:
1在不偷窃第一个房子的情况下(即 nums[1:]nums[1:]),最大金额是 p1
2在不偷窃最后一个房子的情况下(即 nums[:n-1]nums[:n−1]),最大金额是 p2
综合偷窃最大金额: 为以上两种情况的较大值,即 max(p1,p2)
class Solution {
public:
int rob(vector<int>& nums) {
if(nums.size() <= 1) return nums.empty() ? 0 : nums[0];
return max(robRange(nums, 0, nums.size()-1), robRange(nums, 1, nums.size()));
}
int robRange(vector<int>& nums, int left, int right){
if(right - left <= 1) return nums[left];
vector<int> dp(right, 0);
dp[left] = nums[left]; dp[left+1] = max(nums[left], nums[left+1]);
for(int i=left+2; i<right; i++)
dp[i] = max(nums[i]+dp[i-2], dp[i-1]);
return dp.back();
}
};
4. 信件错排
题目描述:有 N 个 信 和 信封,它们被打乱,求错误装信方式的数量。
定义一个数组 dp 存储错误方式数量,dp[i] 表示前 i 个信和信封的错误方式数量。假设第 i 个信装到第 j 个信封里面,而第 j 个信装到第 k 个信封里面。根据 i 和 k 是否相等,有两种情况:
- i==k,交换 i 和 j 的信后,它们的信和信封在正确的位置,但是其余 i-2 封信有 dp[i-2] 种错误装信的方式。由于 j 有 i-1 种取值,因此共有 (i-1)*dp[i-2] 种错误装信方式。
- i != k,交换 i 和 j 的信后,第 i 个信和信封在正确的位置,其余 i-1 封信有 dp[i-1] 种错误装信方式。由于 j 有 i-1 种取值,因此共有 (i-1)*dp[i-1] 种错误装信方式。
综上所述,错误装信数量方式数量为:
5. 母牛生产
题目描述:假设农场中成熟的母牛每年都会生 1 头小母牛,并且永远不会死。第一年有 1 只小母牛,从第二年开始,母牛开始生小母牛。每只小母牛 3 年之后成熟又可以生小母牛。给定整数 N,求 N 年后牛的数量。
第 i 年成熟的牛的数量为:
矩阵路径
1. 矩阵的最小路径和
64. Minimum Path Sum (Medium)
[[1,3,1],
[1,5,1],
[4,2,1]]
Given the above grid map, return 7. Because the path 1→3→1→1→1 minimizes the sum.
题目描述:求从矩阵的左上角到右下角的最小路径和,每次只能向右和向下移动。
class Solution {
public:
int minPathSum(vector<vector<int>>& grid) {
int m = grid.size(), n = grid[0].size();
for(int i=0; i<m; i++){
for(int j=0; j<n; j++){
if(i==0 && j>0)
grid[i][j] = grid[i][j] + grid[i][j-1];
if(j==0 && i>0)
grid[i][j] = grid[i][j] + grid[i-1][j];
if(i>0 && j>0)
grid[i][j] = grid[i][j] + min(grid[i-1][j], grid[i][j-1]);
}
}
return grid[m-1][n-1];
}
};
2. 矩阵的总路径数
62. Unique Paths (Medium)
题目描述:统计从矩阵左上角到右下角的路径总数,每次只能向右或者向下移动。

class Solution {
public:
int uniquePaths(int m, int n) {
int path[m][n];
for(int i=0; i<m; i++){
for(int j=0; j<n; j++){
if(i==0 || j==0) path[i][j] = 1;
else path[i][j] = path[i-1][j] + path[i][j-1];
}
}
return path[m-1][n-1];
}
};
也可以直接用数学公式求解,这是一个组合问题。机器人总共移动的次数 S=m+n-2,向下移动的次数 D=m-1,那么问题可以看成从 S 中取出 D 个位置的组合数量,这个问题的解为 C(S, D)。
public int uniquePaths(int m, int n) {
int S = m + n - 2; // 总共的移动次数
int D = m - 1; // 向下的移动次数
long ret = 1;
for (int i = 1; i <= D; i++) {
ret = ret * (S - D + i) / i;
}
return (int) ret;
}
数组区间
1. 数组区间和
303. Range Sum Query - Immutable (Easy)
Given nums = [-2, 0, 3, -5, 2, -1]
sumRange(0, 2) -> 1
sumRange(2, 5) -> -1
sumRange(0, 5) -> -3
求区间 i ~ j 的和,可以转换为 sum[j + 1] - sum[i],其中 sum[i] 为 0 ~ i - 1 的和。
class NumArray {
public:
vector<int> dp;
NumArray(vector<int>& nums) {
dp = nums;
for(int i=1; i<nums.size(<
本文详细探讨了LeetCode中涉及动态规划的算法题目,涵盖斐波那契数列、强盗抢劫、股票交易等多个场景,通过动态规划解决这些问题,降低时间复杂度,提高解题效率。
最低0.47元/天 解锁文章
401

被折叠的 条评论
为什么被折叠?



