746. 使用最小花费爬楼梯
题目描述
解题思路
要求到达楼顶的最小花费【也就是下标为3时】
1. dp数组定义dp[i]【到达下标i的位置所需要的花费】:
下标记录到哪个台阶
数值记录最小的消耗
2. 递推公式:
等于dp[i-1]跳了一步:dp[i-1] + cost[i-1]【可以理解为到达i-1位置之前的最小总花费加上当前从i-1位置继续再往上跳的花费】
或者等于dp[i-2]跳了两步得到:dp[i-2] + cost[i-2]
因为本题要求的是求最小的花费,而题目允许两种跳法,因此等于两种的更小的那个
因此dp[i] = min(dp[i-1] + cost[i-1], dp[i-2] + cost[i-2])
3. 初始化:
因为最初可以从位置0或1开始,所以初始化dp[0]和dp[1]即可,数值为0【因为根据dp数组定义,因此dp[0]和dp[1]代表到达下标0或1的位置所需要的花费,没有跳则没有花费,也就是0】
4. 遍历顺序:
因为递推公式是后面的元素根据前面的元素来递推【i可以由i-2或i-1得到】,所以正常从前往后遍历即可
5. 举例推导dp数组:
引用自代码随想录对应题的图辅助理解
代码
class Solution {
public int minCostClimbingStairs(int[] cost) {
// 1.dp数组定义:到达下标i的位置所需要最小花费【加1是因为根据数组定义所以还需要记录一个下标为楼顶最小花费】
int[] dp = new int[cost.length + 1];
// 3.初始化:根据递推公式因此只需初始化0和1索引
dp[0] = 0;
dp[1] = 0;
// 4.遍历顺序【0和1位置已经初始化,从2开始】
for (int i = 2; i <= cost.length; i++) {
// 2.递推公式:dp数组可通过向上爬一个或两个台阶得到
dp[i] = Math.min(dp[i-1] + cost[i-1], dp[i-2] + cost[i-2]);
}
// 返回dp数组的最后一个元素
return dp[cost.length];
}
}
198.打家劫舍
题目描述
解题思路
线性数组相邻的房间不能偷
该房子是否能偷取决于前一个和前二个房屋是否偷的情况【动态选择性的结果】
1. dp数组定义
包含下标i之前【包含下标i当前房子】偷的最大的总和
2. 递推公式【哪些步能够推导得到dp[i]】
偷当前i:dp[i-2] + num[i]
不偷当前i:dp[i-1]
因此dp[i] = max(dp[i-2] + num[i], dp[i-1] )
3. 初始化
i可以由i-2或i-1得到,因此需要初始化dp[0]和dp[1]
dp[0] = num[0]
dp[1] = max(num[0], num[1])
4. 遍历顺序
因为递推公式是后面的元素根据前面的元素来递推【i可以由i-2或i-1得到】,所以正常从前往后遍历即可(for (i = 2; i < num.size(); i++))
5. 举例推导dp数组
引用自代码随想录对应题的图辅助理解
代码
记得先根据题意处理数组的特殊情况确保不会出现ArrayIndexOutOfBoundsException报错
class Solution {
public int rob(int[] nums) {
// 根据题意1 <= nums.length <= 100说明最短数组可能只有一个元素,处理特殊情况
if (nums.length == 1) return nums[0];
// 1.dp数组定义:包含下标i及之前偷的最大的总和
int[] dp = new int[nums.length];
// 3.初始化:根据递推公式只需要初始化索引0和1位置
dp[0] = nums[0];
dp[1] = Math.max(dp[0], nums[1]);
// 4.遍历顺序:从前向后遍历,从第二个开始
for (int i = 2; i < nums.length; i++) {
// 2.递推公式【相邻不能偷】:偷当前或不偷当前
dp[i] = Math.max(dp[i-2] + nums[i], dp[i-1]);
}
// 返回dp数组的最后一个元素
return dp[nums.length - 1];
}
}
213.打家劫舍II
题目描述
解题思路
把环形问题展开成线性问题,然后单独考虑首尾元素情况
相邻的房间不能偷,以及首尾相连即相邻不能偷
三种情况:
1. 不考虑首尾元素,只考虑中间部分:
2. 考虑首元素,但不考虑尾元素
3. 考虑尾元素,但不考虑首元素
情况2和情况3包含了情况1因为都考虑了中间部分【考虑的部分决定了遍历的范围】
因此只需要求情况2和情况3取最大值即可,情况1不需要单独再求了【因为23情况是考虑了1情况加上首元素或者尾元素偷或不偷,而偷或不偷取决于递推公式】
代码
class Solution {
public int rob(int[] nums) {
// 根据题意1 <= nums.length <= 100说明最短数组可能只有一个元素,处理特殊情况
if (nums.length == 1) return nums[0];
// 和打家劫舍1区别在于首尾元素不能同时取则有三种情况,1.取中间、2.取中间和首元素、3.取中间和尾元素
// 情况2和3包含了1,所以只需考虑情况2和3
// 情况2:
int result1 = robRange(nums, 0, nums.length - 2);
// 情况3:
int result2 = robRange(nums, 1, nums.length - 1);
return Math.max(result1, result2);
}
private int robRange(int[] nums, int start, int end) {
// 加入首尾的判断
if (start == end) return nums[start];
// 打家劫舍1的逻辑:
// 1.dp数组定义:包含下标i及之前偷的最大的总和
int[] dp = new int[nums.length];
// 3.初始化:根据递推公式只需要初始化索引start和start+1位置
dp[start] = nums[start];
dp[start + 1] = Math.max(dp[start], nums[start + 1]);
// 4.遍历顺序:从前向后遍历,从第start+2个开始
for (int i = start + 2; i <= end; i++) {
// 2.递推公式【相邻不能偷】:偷当前或不偷当前
dp[i] = Math.max(dp[i-2] + nums[i], dp[i-1]);
}
// 返回dp数组的最后一个元素
return dp[end];
}
}