198. 打家劫舍
链接
文章链接
题目链接
第一想法
这道题怎么说呢,我认为代码有点问题,但是居然能通过测试...所以我这里我先写我的思路
- 首先是dp数组的含义 dp[i]为第i天最大的偷窃数为dp[i]
- 初始化很简单,因为没偷窃时都是0,但是依据递推公式,所以必须先初始dp[0]=nums[0], dp[1]=Math.max(nums[0], nums[1])
- 递推公式为
dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i])
,这里我理解的为不抢劫第i天,所以最大金额为dp[i-1](抢第i-1天),如果抢第i天,则i-1天不能抢所以只能拿dp[i-2]算 - 举例[1,1,4,1,1,9]结果为[1,1,5,5,6,14]
function rob(nums: number[]): number {
if (nums.length == 1) return nums[0]
let dp: number[] = new Array(nums.length).fill(0)
dp[0] = nums[0]
dp[1] = Math.max(nums[0], nums[1])
for (let i = 2; i < nums.length; i++) {
dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i])
}
return dp[dp.length - 1]
}
虽然这道题写出来了,但是关于递推公式还是有一点困惑的拿dp[i-2]算难道dp[i-2]一定偷吗,就是我举个那个例子,但是居然通过了。
看完文章后的想法
果然,虽然我写对了,但是思路是有大问题的,首先是dp[i]的含义dp[i]为包含当前i天时能偷取的最大金额。其次,就是递推公式的理解,首先是不抢第i天,则金额为dp[i-1](这里和抢不抢i-1天没关系),如果抢第i天的话,则一定一定不能拿i-1算,如果拿i-1算可能i-1天也被抢了。所以那dp[i-2]天,因为不管抢不抢i-2天都可以拿i-2算。下面来个图例:
思考
这道题虽然写出来了,但是思想有问题,陷入自己的陷阱当中了,看完文章后就发现自己的问题所在了。这道题的难点在于dp数组的理解以及递推公式。只要把这两个搞清楚这道题就不算太难了。
213. 打家劫舍 II
链接
文章链接
题目链接
第一想法
emmm,刚刚做了打家劫舍,所以看到这个就和原来就一个不同,就是首和尾相连,所以我的想法是搞一个辅助数组,判断最后一个元素-2天是否使用了第一个元素,使用了则返回dp[dp.length-1],详细解释在代码随想录中展示
//这个代码是无法通过用例的
function rob(nums: number[]): number {
if (nums.length == 1) return nums[0]
let dp: number[] = new Array(nums.length).fill(0)//dp数组
let Boo: boolean[] = new Array(nums.length).fill(false)//辅助函数 判断是否使用了第一个元素
dp[0] = nums[0]
dp[1] = Math.max(nums[0], nums[1])
Boo[0] = true//初始化
Boo[1] = nums[0] > nums[1]//初始化 如果不偷下标为1的房子 则说明需要那0天算 所以也要设置为true
for (let i = 2; i < nums.length; i++) {
if (i == nums.length - 1) {
if (dp[i - 1] >= dp[i - 2] + nums[i] || Boo[i - 2]) return Math.max(dp[i - 1], nums[i])
else return dp[i - 2] + nums[i] //可以偷最后一天
}
dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i])
Boo[i] = dp[i - 1] > dp[i - 2] + nums[i] ? Boo[i - 1] : Boo[i - 2]//判断偷i天后是否是偷了第0天
}
return dp[dp.length - 1]
}
看完文章后的想法
emmmm....还是一样,自己把考虑房子和偷房子概念搞混了,
考虑当前的房子不一定偷当前的房子!!!!
考虑当前的房子不一定偷当前的房子!!!!
考虑当前的房子不一定偷当前的房子!!!!
所以我的那种想法是完全错误的,依照题目,最后一天和第一天不能同时考虑,如果同时考虑的话就有可能两个同时被偷,所以要分开考虑第一天和最后一天,如图:
考虑第一天,则不考虑最后一天
考虑最后一天则不考虑第一天:
代码如下
function rob(nums: number[]): number {
const foo = (start: number, end: number, nums: number[]) => {//foo就是初级版的打家劫舍 start和end用来控制起始位置和终止位置
if (nums.length == 1) return nums[0]
let dp: number[] = new Array(nums.length).fill(0)
dp[start] = nums[start]
dp[start + 1] = Math.max(nums[start], nums[start + 1])
for (let i = start + 2; i < end; i++) {
dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i])
}
return dp[end - 1]
}
//通过比较两种情况的最大值来确定最高偷取金额
return Math.max(foo(0, nums.length - 1, nums), foo(1, nums.length, nums))
}
思考
还是没考虑清楚考虑房子和偷窃房子,只要考虑清楚,就没啥问题了。同时这道题因为首尾是相连的,所以需要分开考虑这两种情况,自己是没想到的,需要收藏回看。
337. 打家劫舍 III
链接
文章链接
题目链接
第一想法
emmm...考虑的问题少了。我看到事例我就想到了如果这一层的某个元素选了,那么这一层的元素都选上肯定是最大的,因为当前节点选了,说明上一层的父节点不能选,结果就按照这个思路写了,发现报错....因为我没考虑这种情况:
这是情况最大值是7而不是6,所以即使是在相邻的两层也是可以选的,只要不是直接相连的就可以,代码如下
//下面代码是无法通过全部测试用例得到
function rob(root: TreeNode | null): number {
let dp: number[] = []
let curr: TreeNode[] = []
if (root) curr.push(root)
while (curr.length > 0) {
let size: number = curr.length
let sum: number = 0
dp.push(0)
while (size--) {
let node: TreeNode = curr.shift()!
sum += node.val
if (node.left) curr.push(node.left)
if (node.right) curr.push(node.right)
}
if (dp.length == 1) dp[0] = sum
else if (dp.length == 2) dp[1] = Math.max(dp[0], sum)
else {
dp[dp.length - 1] = Math.max(dp[dp.length - 2], dp[dp.length - 3] + sum)
}
}
return dp[dp.length - 1]
}
看完文章后的想法
果然,文章还是想的全面,这道题是跟树结合起来要考的,而且它的递归函数比较奇怪,就两个dp[0]是存放不偷当前的价值,dp[1]是存放偷当前的房子之后的价值,而且这道题运用到后序遍历的知识,因为返回值为dp,所以代码如下:
function rob(root: TreeNode | null): number {
const foo: (root: TreeNode | null) => number[] = (root) => {
if (root == null) return [0, 0]//dp数组 遇到null说明是最底层了
let left: number[] = foo(root.left)//左子树的结果
let right: number[] = foo(root.right)//右子树的结果
//偷当前节点
let val1: number = left[0] + right[0] + root.val
//不偷当前节点
let val2: number = Math.max(left[0], left[1]) + Math.max(right[0], right[1])
return [val2, val1]
}
return Math.max(...foo(root))
}
举例:
思考
这道题其实是不难的,但是我确实没想到,这是第一次遇到数的动态规划,第一次遇到这种思想
今日总结
今天耗时2.5小时,今天比较失败,三道题就会最简单的一道,而且打家劫舍的思想我没有想清楚,考虑房子和偷房子是两个概念。今天的第三道题是值得纪念的一道题,第一次遇到动态规划和树结合,思想值得学习。