动态规划
Solution70. 爬楼梯

为什么 dp[i] = dp[i-1] + dp[i-2]?
由于每次你可以爬1或2个台阶,要爬到第i阶楼梯,你的最后一步只有两种可能:
从第 i-1 阶爬 1 个台阶上来。
从第 i-2 阶爬 2 个台阶上来。
换句话说:
如果你最后一步爬了 1 个台阶,那么你之前一定是在第 i-1 阶。
如果你最后一步爬了 2 个台阶,那么你之前一定是在第 i-2 阶。
因此,爬到第i阶的总方法数dp[i] = 从第i-1阶爬1个台阶上来的方法数dp[i-1] + 从第i-2阶爬2个台阶上来的方法数dp[i-2]
因此,dp[i] = dp[i-1] + dp[i-2]
也就是说不论n此刻是几:你最后一步只能爬 1 个台阶或 2 个台阶。
例子 1:n = 2
爬到第 2 阶有两种方法:①1 阶 + 1 阶 ②直接爬 2 阶。
例子 2:n = 3
爬到第 3 阶有三种方法:
1 阶 + 1 阶 + 1 阶。
1 阶 + 2 阶。
2 阶 + 1 阶。
根据公式:
dp[3] = dp[2] + dp[1]
dp[2] = 2(爬到第 2 阶有两种方法)。
dp[1] = 1(爬到第 1 阶有一种方法)。
所以 dp[3] = 2 + 1 = 3。
例子 3:n = 4
爬到第 4 阶有五种方法:
1 阶 + 1 阶 + 1 阶 + 1 阶。
1 阶 + 1 阶 + 2 阶。
1 阶 + 2 阶 + 1 阶。
2 阶 + 1 阶 + 1 阶。
2 阶 + 2 阶。
根据公式:
dp[4] = dp[3] + dp[2]。
dp[3] = 3(爬到第 3 阶有三种方法)。
dp[2] = 2(爬到第 2 阶有两种方法)。
所以 dp[4] = 3 + 2 = 5。
public class Solution {
public static void main(String[] args) {
int n = 5;
System.out.println(climbStairs(n));
}
public static int climbStairs(int n) {
if (n == 0 || n == 1) {
return 1;
}
int prev1 = 1;
int prev2 = 1;
int result = 0;
for (int i = 2; i <= n; i++) {
result = prev1 + prev2;
prev2 = prev1;
prev1 = result;
}
return result;
}
}
Solution118. 杨辉三角

每一行的第一个数字是 1
每一行的最后一个数字是 1
每一行中间的数字row(j)=prevRow.get(j - 1) + prevRow.get(j)
[1]
[1, 1]
[1, 2, 1]
[1, 3, 3, 1]
[1, 4, 6, 4, 1]
public class Solution {
public static void main(String[] args) {
int numRows = 5;
List<List<Integer>> triangle = generate(numRows);
for (List<Integer> row : triangle) {
System.out.println(row);
}
}
public static List<List<Integer>> generate(int numRows) {
List<List<Integer>> triangle = new ArrayList<>();
for (int rowNum = 0; rowNum < numRows; rowNum++) {
List<Integer> row = new ArrayList<>();
row.add(1);
if (rowNum > 0) {
List<Integer> prevRow = triangle.get(rowNum - 1);
for (int j = 1; j < rowNum; j++) {
row.add(prevRow.get(j - 1) + prevRow.get(j));
}
row.add(1);
}
triangle.add(row);
}
return triangle;
}
}
Solution198. 打家劫舍

定义 dp[i] 表示偷窃到第 i 间房屋时能够获得的最大金额。
状态转移方程:
- 如果偷窃第 i 间房屋,那么不能偷窃第 i-1 间房屋,最大金额为 dp[i-2] + nums[i]。
- 如果不偷窃第 i 间房屋,那么最大金额为 dp[i-1]。
所以:dp[i] = max(dp[i-1], dp[i-2] + nums[i]),如dp[3] = max(dp[2], dp[1] + nums[3])
初始条件:
dp[0] = nums[0](只有一间房屋时,只能偷窃这一间)。
dp[1] = max(nums[0], nums[1])(有两间房屋时,选择金额较大的一间偷窃)。
public class Solution {
public static void main(String[] args) {
int[] nums = {
2, 7, 9, 3, 1};
System.out.println(rob(nums));
}
public static int rob(int[] nums) {
if (nums == null || nums.length == 0) {
return 0;
}
if (nums.length == 1) {
return nums[0];
}
int prev2 = nums[0];
int prev1 = Math.max(nums[0], nums[1]);
int result = prev1;
for (int i = 2; i < nums.length; i++) {
result = Math.max(prev1, prev2 + nums[i]);
prev2 = prev1;
prev1 = result;
}
return result;
}
}
Solution279. 完全平方数

我们需要找到和为 n 的完全平方数的最少数量。例如:
- 如果 n = 12,我们可以用 4 + 4 + 4(即 2^2 + 2^2 + 2^2),所以最少需要 3 个完全平方数。
- 如果 n = 13,我们可以用 9 + 4(即 3^2 + 2^2),所以最少需要 2 个完全平方数。
dp[i] 表示和为 i 的完全平方数的最少数量。
对于每个 i,我们尝试用 所有可能的完全平方数 来组成 i。
假设我们选择了一个完全平方数 j*j,那么剩下的部分就是 i - j*j。
我们需要找到 i - j*j 的最少完全平方数数量,然后加上 1(因为我们已经用了一个 j*j)。
状态转移方程
对于每个 i,我们遍历所有可能的 j(满足 j*j <= i)。
对于每个 j,我们计算 dp[i - j*j] + 1,表示用 j*j 组成 i 的最少数量。
我们取所有可能的 j 中的最小值,作为 dp[i] 的值。
初始条件:
dp[0] = 0(和为 0 的完全平方数数量为 0)。
例子 2:n = 12
我们需要计算 dp[12],可能的j值:1、2 和 3(因为 1*1 = 1、2*2 = 4 和 3*3 = 9)。
对于 j = 1:
dp[12 - 1*1] + 1 = dp[11] + 1。
假设 dp[11] = 3(即 9 + 1 + 1)。
所以 dp[12] = min(∞, 3 + 1) = 4。
对于 j = 2:
dp[12 - 2*2] + 1 = dp[8] + 1。
假设 dp[8] = 2(即 4 + 4)。
所以 dp[12] = min(4, 2 + 1) = 3。
对于 j = 3:
dp[12 - 3*3] + 1 = dp[3] + 1。
假设 dp[3] = 3(即 1 + 1 + 1)。
所以 dp[12] = min(3, 3 + 1) = 3。
最终 dp[12] = 3(即 4 + 4 + 4)。
public class Solution {
public static void main(String[] args) {
int n = 12;
System.out.println(numSquares(n));
}
public static int numSquares(int n) {
int[] dp = new int[n + 1];
Arrays.fill(dp, Integer.MAX_VALUE);
dp[0] = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j * j <= i; j++) {
dp[i] = Math.min(dp[i], dp[i - j * j] + 1);
}
}
return dp[n];
}
}
Solution322. 零钱兑换

定义 dp[i] 表示凑成金额 i 所需的最少硬币数量。
初始条件:
dp[0] = 0(凑成金额 0 需要 0 个硬币)。
状态转移方程:
对于每个金额 i,遍历所有硬币面额 coin:
如果 i >= coin,则 dp[i] = min(dp[i], dp[i - coin] + 1)。
最终返回 dp[amount],如果 dp[amount] 仍然是初始值(表示无法凑成),则返回 -1。
执行流程演示
我们以 coins = [1, 2, 5] 和 amount = 11 为例,演示代码的执行流程。
初始状态
dp = [0, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12]
步骤 1: 计算 dp[1]
遍历硬币 1:
dp[1] = min(12, dp[0] + 1) = min(12, 1) = 1
dp = [0, 1, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12]
步骤 2: 计算 dp[2]
遍历硬币 1:
dp[2] = min(12, dp[1] + 1) = min(12, 2) = 2
遍历硬币 2:
dp[2] = min(2, dp[0] + 1) = min(2, 1) = 1
dp = [0, 1, 1, 12, 12, 12, 12, 12, 12, 12, 12, 12]
步骤 3: 计算 dp[3]
遍历硬币 1:
dp[3] = min(12, dp[2] + 1) = min(12, 2) = 2
遍历硬币 2:
dp[3] = min(2, dp[1] + 1) = min(2, 2) = 2
dp = [0, 1, 1, 2, 12, 12, 12, 12, 12, 12, 12, 12]
步骤 4: 计算 dp[4]
遍历硬币 1:
dp[4] = min(12, dp[3] + 1) = min(12, 3) = 3
遍历硬币 2:
dp[4] = min(3, dp[2] + 1) = min(3, 2) = 2
dp = [0, 1, 1, 2, 2, 12, 12, 12, 12, 12, 12, 12]
步骤 5: 计算 dp[5]
遍历硬币 1:
dp[5] = min(12, dp[4] + 1) = min(12, 3) = 3
遍历硬币 2:
dp[5] = min(3, dp[3] + 1) = min(3, 3) = 3
遍历硬币 5:
dp[5] = min(3, dp[0] + 1) = min(3, 1) = 1
dp = [0, 1, 1, 2, 2, 1, 12, 12, 12, 12, 12, 12]
步骤 6: 计算 dp[6]
遍历硬币 1:
dp[6] = min(12, dp[5] + 1) = min(12, 2) = 2
遍历硬币 2:
dp[6] = min(2, dp[4] + 1) = min(2, 3) = 2
遍历硬币 5:
dp[6] = min(2, dp[1] + 1) = min(2, 2) = 2
dp = [0, 1, 1, 2, 2, 1, 2, 12, 12, 12, 12, 12]
步骤 7: 计算 dp[7]
遍历硬币 1:
dp[7] = min(12, dp[6] + 1) = min(12, 3) = 3
遍历硬币 2:
dp[7] = min(3, dp[5] + 1) = min(3, 2) = 2
遍历硬币 5:
dp[7] = min(2, dp[2] + 1) = min(2, 2) = 2
dp = [0, 1, 1, 2, 2, 1, 2, 2, 12, 12, 12, 12]
步骤 8: 计算 dp[8]
遍历硬币 1:
dp[8] = min(12, dp[7] + 1) = min(12, 3) = 3
遍历硬币 2:
dp[8] = min(3, dp[6] + 1) = min(3, 3) = 3
遍历硬币 5:
dp[8] = min(3, dp[3] + 1) = min(3, 3) = 3
dp = [0, 1, 1, 2, 2, 1, 2, 2, 3, 12, 12, 12]
步骤 9: 计算 dp[9]
遍历硬币 1:
dp[9] = min(12, dp[8] + 1) = min(12, 4) = 4
遍历硬币 2:
dp[9] = min(4, dp[7] + 1) = min(4, 3) = 3
遍历硬币 5:
dp[9] = min(3, dp[4] + 1) = min(3, 3) = 3
dp = [0, 1, 1, 2, 2, 1, 2, 2, 3, 3, 12, 12]
步骤 10: 计算 dp[10]
遍历硬币 1:
dp[10] = min(12, dp[9] + 1) = min(12, 4) = 4
遍历硬币 2:
dp[10] = min(4, dp[8] + 1) = min(4, 4) = 4
遍历硬币 5:
dp[10] = min(4, dp[5] + 1) = min(4, 2) = 2
dp = [0, 1, 1, 2, 2, 1, 2, 2, 3, 3, 2, 12]
步骤 11: 计算 dp[11]
遍历硬币 1:
dp[11] = min(12, dp[10] + 1) = min(12, 3) = 3
遍历硬币 2:
dp[11] = min(3, dp[9] + 1) = min(3, 4) = 3
遍历硬币 5:
dp[11] = min(3, dp[6] + 1) = min(3, 3) = 3
dp = [0, 1, 1, 2, 2, 1, 2, 2, 3, 3, 2, 3]
最终状态
dp[11] = 3,即凑成总金额 11 所需的最少硬币数量为 3。
public class Solution {
public static void main(String[] args) {
int[] coins = {
1, 2, 5};
int amount = 11;
System.out.println(coinChange(coins, amount));
}
public static int coinChange(int[] coins, int amount) {