最近很久没有刷算法了感觉脑子被糊住了,因此复习一些基本的算法,路线参考自Claude Ops4.7体感下来确实是很好的路线。
Day1主要是从一维到环形到树形,理解"选 / 不选"两个状态,但是个人感觉这一部分其实是最简单的DP题目,尤其是再定义转移公式的时候完全不用动脑子
| 题号 | 题名 | 难度 | 备注 |
|---|---|---|---|
| 70 | 爬楼梯 | 简单 | 热身 5 min,写出空间优化版 |
| 198 | 打家劫舍 | 中等 | Hot 100 复习,这次必须写出滚动数组版 |
| 213 | 打家劫舍 II | 中等 | 环形 → 拆成两次线性 |
| 337 | 打家劫舍 III | 中等 | 树形 DP 入门题,返回二元组 [偷, 不偷] |
LC 70 爬楼梯
- 题目

出现的问题主要是这里dp[0]的值到底是多少:
class Solution {
public int climbStairs(int n) {
// dp[i]表示从0到第i阶有多少种方法
if(n < 2)return 1;
int[] dp = new int[n+1];
dp[0] = 0; // 这里应该是1
dp[1] = 1;
for(int i = 2;i <= n;i++){
dp[i] = dp[i-1] + dp[i-2];
}
return dp[n];
}
}
因为DP 里的 dp[0] 不是在问"动作",而是在问"有多少种方案能让我停在第 0 阶 "。那因此答案是:有 1 种 ------ 就是「啥也不做」这一种
当然其实也可以从 dp[2] 开始定义初值(绕开 dp[0] 的哲学问题)
第一题主要出现的问题就是再定义dp[0]的时候其实是没有想清楚的,dp定义是从0到0没有别的方法因此是1,但是不小心理解为0到0不需要操作因此不存在方法,这个确实仔细想想是有问题的
LC 198 打家劫舍
-
题目

出现的问题主要是这里dp[1]的值的计算方式:
class Solution {
public int rob(int[] nums) {
// dp[i]表示在i号房屋之前(从0开始)可以偷到的最多的钱
if(nums.length < 2)return nums[0];
int[] dp = new int[nums.length];
dp[0] = nums[0];
dp[1] = nums[1]; // 这里应该是dp[1] = Math.max(nums[0],nums[1]);
for(int i = 2;i < dp.length;i++){
dp[i] = Math.max(dp[i-1],dp[i-2]+nums[i]);
}
return dp[nums.length-1];
}
}
主要是其实就算是dp[1]其实也是符合**dp[i] = Math.max(dp[i-1],dp[i-2]+nums[i]);**的关系式子的,只是因为没有dp[-1]而单独拎出来的而已。因此主要是初始化的问题
这个题其实更多的是自己粗心导致的问题,再dp[1]初始化的时候没有想清楚就动笔了,千万要记住dp数组初始化是最关键的两个步骤中的一个一定需要仔细了
LC 213 打家劫舍II →成环
-
题目

class Solution {
public int rob(int[] nums) {
// 两种情况进行讨论
int[] dp = new int[nums.length];
// 先考虑如果第一家开始偷了
if(nums.length < 2)return nums[0];
dp[0] = nums[0];
dp[1] = nums[0];
for(int i = 2;i < nums.length - 1;i++){
dp[i] = Math.max(dp[i-1],dp[i-2]+nums[i]);
}
int max = dp[nums.length - 2]; // 主要需要注意这里
//如果第一家不去偷
dp[0] = 0;
dp[1] = nums[1];
for(int i = 2;i < nums.length;i++){
dp[i] = Math.max(dp[i-1],dp[i-2]+nums[i]);
}
max = Math.max(max,dp[nums.length - 1]);
return max;
}
}
主要的错误就是**第一个地方的max应该是dp[nums.length - 2],因为最后一个不允许偷盗。**而下面的第一家不偷,因此最大值才在dp[nums.length - 1]
这个题目分类讨论的想法我是想到了的,但是对于这里int max = dp[nums.length - 2]这一部分的时候定义一定需要注意,因为我第一次就是写错了的
而成环和不成环其实很多题目都是用这种思想来进行处理的->使用分类讨论等方法将其转换成普通的不成环的题目,然后正常求解
LC 337 打家劫舍III →数状
-
题目

class Solution {
public int rob(TreeNode root) {
int[] x = robSub(root);
return Math.max(x[0],x[1]);
}private int[] robSub(TreeNode root){ int[] x = new int[2]; if(root == null)return x; // int[0]表示不偷当前节点 int[] l = robSub(root.left); int[] r = robSub(root.right); x[0] = l[1] + r[1]; // 这个地方不应该强制子节点去偷,而是选择里面最大的方案 // x[0] = Math.max(l[0],l[1]) + Math.max(r[0],r[1]); x[1] = l[0] + r[0] + root.val; return x; }}
这个题目最主要的问题就是不应该是x[0] = l[1] + r[1];,而应该x[0] = Math.max(l[0],l[1]) + Math.max(r[0],r[1]); 因为如果是前者就相当于不偷当前节点但是强制偷子节点,显然这个不一定是最大的,因此这里需要一个继续的取舍抉择,有一个最大值
这个题目虽然说是dp问题,而且确实使用了数组进行了记忆,但是其实这个更多意义上是一个标准的树的递归求解思路,这里递归求解的时候一定需要注意这里面的公式呀,因为我就是这么出错的呢,没办法还是太菜了