class Solution {
// 思路:基于动态规划
public int climbStairs(int n) {
// 处理边界条件,如果台阶数为 1,只有一种方法
if (n == 1) {
return 1;
}
// 用数组记录 n 个台阶的方法数,其中跳过了索引 0
int[] result = new int[n + 1];
// 台阶数为 1 是 1 种方法
result[1] = 1;
// 台阶数为 2 是 2 种方法
result[2] = 2;
// 从台阶数为 3 开始遍历到最后
for (int i = 3; i <= n; i++) {
// 状态转移方程:到达 i 阶台阶的方法数等于到达 i - 1 阶台阶的方法数加上到达 i - 2 阶台阶的方法数
result[i] = result[i - 1] + result[i - 2];
}
// 最后返回 n 阶台阶的方法数即是结果
return result[n];
}
}
class Solution {
public int rob(int[] nums) {
// 1. 检查输入是否为空或长度为 0
if (nums == null || nums.length == 0) {
return 0; // 如果是空数组,返回 0
}
// 2. 如果只有一个房屋,直接返回那个房屋的值
if (nums.length == 1) {
return nums[0]; // 只有一个选择,只能偷这一家
}
// 3. 如果有两个房屋,返回这两个房屋中较大的值
if (nums.length == 2) {
return Math.max(nums[0], nums[1]); // 选择最大值
}
// 4. 创建 dp 数组,用于存储到每个房屋的最大金额
int[] dp = new int[nums.length];
// 5. 初始化第一个房屋的最大值为其自身的值
dp[0] = nums[0];
// 6. 初始化第二个房屋的最大值为前两个房屋的较大值
dp[1] = Math.max(nums[0], nums[1]);
// 7. 从第三个房屋开始进行动态规划计算
for (int i = 2; i < nums.length; i++) {
// 8. dp[i] 表示偷取到第 i 个房屋的最大金额
// - 可以选择偷当前房屋 i,加上之前可以偷的最大金额 (dp[i - 2])
// - 或者不偷当前房屋,继承之前的最大金额 (dp[i - 1])
dp[i] = Math.max(dp[i - 2] + nums[i], dp[i - 1]);
}
// 9. 返回到最后一个房屋的最大偷窃金额
return dp[nums.length - 1];
}
}
import java.util.HashSet; // 导入 HashSet 类
import java.util.List; // 导入 List 接口
import java.util.Set; // 导入 Set 接口
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
// 1. 将单词字典转换为 HashSet,以便快速查找
Set<String> wordSet = new HashSet<>(wordDict);
// 2. 创建一个 dp 数组,dp[i] 表示 s 的前 i 个字符是否可以拆分为字典中的单词
boolean[] dp = new boolean[s.length() + 1];
dp[0] = true; // 3. 空字符串可以被拆分,初始化 dp[0] 为 true
// 4. 遍历 dp 数组,从 dp[1] 到 dp[s.length()]
for (int i = 1; i < dp.length; i++) {
// 5. 遍历从 0 到 i 的所有可能的 j
for (int j = 0; j < i; j++) {
// 6. 如果 dp[j] 为 true,且 s 的子串 s[j:i] 在字典中
if (dp[j] && wordSet.contains(s.substring(j, i))) {
dp[i] = true; // 7. 更新 dp[i] 为 true
break; // 8. 找到拆分方式,跳出内层循环
}
}
}
// 9. 返回最终结果,判断整个字符串 s 是否可以被拆分
return dp[s.length()];
}
}
class Solution {
public int coinChange(int[] coins, int amount) {
// 定义最大值,这里的 amount + 1 作为一个超出可能结果范围的值,用于后续判断无法兑换的情况
int max = amount + 1;
// 定义金额动态数组 dp,存储每个金额所需要的最小硬币数量
int[] dp = new int[amount + 1];
// 将 dp 数组初始化为一个根本无法达到的大值,如果后续判断不满足,说明找不开
Arrays.fill(dp, max);
// 边界条件,金额为 0 时不需要任何硬币就能兑换,所以 dp[0] = 0
dp[0] = 0;
// 数额为 amount,从 1 开始算各个金额所需要的最新面值
for (int i = 1; i <= amount; i++) {
for (int j = 0; j < coins.length; j++) {
// 需要兑换的金额必须得大于等于现有面值吧
if (i >= coins[j]) {
// dp[i] 表示兑换金额为 i 时所需的最小硬币数量,dp[i - coins[j]] + 1 表示使用当前面值 coins[j] 时的可能最小硬币数量
// Math.min 取两者中的较小值更新 dp[i]
dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1);
}
}
}
// 如果金额为 max(amount + 1)说明没兑换出去,返回 -1,否则就是 dp[amount],即兑换金额为 amount 时所需的最小硬币数量
return dp[amount] > amount? -1 : dp[amount];
}
}
class Solution {
// 接受一个整数数组 nums 作为输入,返回数组中最长递增子序列的长度
public int lengthOfLIS(int[] nums) {
// 如果输入数组 nums 的长度为 0,则返回 0,因为空数组没有递增子序列
if (nums.length == 0) {
return 0;
}
// 创建一个整数数组 dp,长度与 nums 相同。dp[i] 表示以 nums[i] 结尾的最长递增子序列的长度
int[] dp = new int[nums.length];
// 将 dp[0] 设置为 1,因为以第一个元素结尾的最长递增子序列至少为 1(它本身)
dp[0] = 1;
// 创建一个变量 maxans,用于记录迄今为止找到的最长递增子序列的长度,同时初始化为 1
int maxans = 1;
// 外层循环遍历数组,从第二个元素开始,遍历数组 nums 的每个元素
for (int i = 0; i < nums.length; i++) {
// 在内层循环之前,将 dp[i] 设置为 1,表示以 nums[i] 结尾的子序列至少包含它本身
dp[i] = 1;
// 遍历从 0 到 i - 1 的每个元素 j,判断当前元素 nums[i] 是否大于 nums[j]
for (int j = 0; j < i; j++) {
// 如果 nums[i] 大于 nums[j],那么 nums[i] 可以添加到以 nums[j] 结束的递增子序列中
if (nums[i] > nums[j]) {
// 更新 dp[i] 为 dp[j] + 1 和当前 dp[i] 中的最大值,这样可以找到以 nums[i] 结尾的最长递增子序列的长度
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
// 在内层循环结束后,更新 maxans,记录迄今为止的最长递增子序列的长度。
maxans = Math.max(dp[i], maxans);
}
// 返回 maxans,表示找到的最长递增子序列的长度
return maxans;
}
}