递归和动态规划都是将原问题拆成多个子问题然后求解,他们之间最本质的区别是,动态规划保存了子问题的解,避免重复计算。
重点都是要找到转移方程
Fibonacci:
在上述方法中,我们使用 dp 数组,其中 dp[i]=dp[i-1]+dp[i-2]。可以很容易通过分析得出 dp[i]其实就是第 ii个斐波那契数。
Fib(n)=Fib(n−1)+Fib(n−2)
重点是确定pre1和pre2,以及最后要返回哪个
70. 爬楼梯(e)
198. 打家劫舍(e)
213. 打家劫舍2(m)
强盗在环形区抢劫
环状排列意味着第一个房子和最后一个房子中只能选择一个偷窃,因此可以把此环状排列房间问题约化为两个单排排列房间子问题:
在不偷窃第一个房子的情况下(即nums[1:]),最大金额是 p_1
在不偷窃最后一个房子的情况下(即 nums[:n−1]),最大金额是 p_2
综合偷窃最大金额: 为以上两种情况的较大值,即 max(p1,p2)。
public int rob(int[] nums) {
if (nums == null || nums.length <= 0) {
return 0;
}
if (nums.length == 1) {
return nums[0];
}
int n = nums.length;
return Math.max(robHelper(nums,0,n-1),robHelper(nums,1,n));
}
private int robHelper(int[] nums, int start, int end) {
int pre2 = 0 , pre1 = nums[start];
for (int i = start + 1; i < end; i++) {
int cur = Math.max(pre2+nums[i],pre1);
pre2 = pre1;
pre1 = cur;
}
return pre1;
}
}
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] 种错误装信方式。
综上所述,错误装信数量方式数量为:
矩阵路径:
64. 最小路径和(m)
重点:一维动态规划,dp 数组的大小和行大小相同。这是因为对于某个固定状态,只需要考虑下方和右侧的节点。
题目要求,只能向右或向下走,换句话说,当前单元格 (i,j)(i,j) 只能从左方单元格 (i-1,j)(i−1,j) 或上方单元格 (i,j-1)(i,j−1) 走到,因此只需要考虑矩阵左边界和上边界。
public int minPathSum(int[][] grid) {
if (grid.length == 0 || grid[0].length == 0) {
return 0;
}
int m = grid.length, n = grid[0].length;
int[] dp = new int[n];
for (int i = 0 ;i < m; i++) {
for (int j = 0 ;j < n;j ++) {
if (i == 0 && j == 0) {
dp[j] = grid[i][j];
}
// 第一行,只能从左边来
else if (i == 0) {
dp[j] = dp[j-1] + grid[i][j];
}
// 第一列,只能从上边来
else if (j == 0) {
dp[j] = dp[j] + grid[i][j];
}
else {
dp[j] = Math.min(dp[j],dp[j-1])+grid[i][j];
}
}
}
return dp[n-1];
}
62. 不同路径(m)
从左边来的dp[i-1][j]+从上边来的dp[i][j-1] public int uniquePaths(int m, int n) { int[][] dp = new int[m][n]; for (int i = 0; i < n; i++) dp[0][i] = 1; for (int i = 0; i < m; i++) dp[i][0] = 1; for (int i = 1; i < m; i++) { for (int j = 1; j < n; j++) { dp[i][j] = dp[i - 1][j] + dp[i][j - 1]; } } return dp[m - 1][n - 1]; }
使用二维数组的时候,dp[i][j] = dp[i-1][j] + dp[i][j-1] ,每一个格子的数据等于上面一个格子加左边格子的数据。可以想象一下,计算一行数据的时候,直接把上面一行的数据搬下来,然后每个格子就等于前一个格子的数据加上当前格子的数据。
public int uniquePaths(int m, int n) { int[] cur = new int[n]; Arrays.fill(cur,1); for (int i = 1; i < m;i++){ for (int j = 1; j < n; j++){ cur[j] += cur[j-1] ; } } return cur[n-1]; }