目录
- 198.打家劫舍
- 1、题目描述
- 2、思路
- 3、code
- 4、复杂度分析
- 213.打家劫舍II
- 1、题目描述
- 2、思路
- 3、code
- 4、复杂度分析
- 337. 打家劫舍 III
- 1、题目描述
- 2、思路
- 3、code
- 4、复杂度分析
198.打家劫舍
题目链接:添加链接描述
1、题目描述
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
示例 1:
输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
2、思路
重叠子问题: 当前房屋偷与不偷取决于 前一个房屋和前两个房屋是否被偷了
1️⃣ 确定dp数组(dp table)以及下标的含义
dp[i]:考虑下标i(包括i)以内的房屋,最多可以偷窃的金额为dp[i]。
2️⃣ 确定递推公式
对于房屋i,有两种选择:
- 偷:dp[i] = dp[i-2] + nums[i]
- 不偷:dp[i] = dp[i-1]
- d p [ i ] = m a x ( d p [ i − 2 ] + n u m s [ i ] , d p [ i − 1 ] ) dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]) dp[i]=max(dp[i−2]+nums[i],dp[i−1])
3️⃣ 初始化
- dp[0] = nums[0]
- dp[1] = max(nums[0],nums[1])
4️⃣ 遍历顺序:从前到后
3、code
class Solution:
def rob(self, nums: List[int]) -> int:
if len(nums) == 1:
return nums[0]
# dp[i] 代表偷到第i个房屋能偷窃的最高金额 i:0,,,n-1
dp = [0] * len(nums) # dp[3]
# 对于房屋i,有两种选择:
# 偷:dp[i] = dp[i-2] + nums[i]
# 不偷:dp[i] = dp[i-1]
dp[0] = nums[0]
dp[1] = max(nums[0],nums[1])
for i in range(2,len(nums)):
dp[i] = max(dp[i-2]+nums[i],dp[i-1])
return dp[-1]
4、复杂度分析
1️⃣ 时间复杂度:
O
(
N
)
O(N)
O(N)
2️⃣ 空间复杂度:
O
(
N
)
O(N)
O(N)
213.打家劫舍II
题目链接:link
1、题目描述
你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。 同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。
给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。
示例 1:
输入:nums = [2,3,2]
输出:3
解释:你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。
2、思路
上一道题是普通的数组,这一道题是成环的数组,如何用普通的数组来表示成环的数组?
普通的数组和成环的数组的区别就是:普通数组可以同时考虑首元素和尾元素,但是成环的数组要分为三种情况:
情况一:考虑不包含首尾元素
情况二:考虑包含首元素,不包含尾元素
情况三:考虑包含尾元素,不包含首元素
🔥 考虑首元素不代表选首元素,“考虑”代表这个元素使有可能被打劫的,但是要根据动态规划的公式来判断是否打劫
情况二 和 情况三 都包含了情况一了,所以只考虑情况二和情况三就可以
3、code
class Solution:
def rob(self, nums: List[int]) -> int:
if len(nums) == 1:
return nums[0]
if len(nums) == 2:
return max(nums[0],nums[1])
res1 = self.robrange(0,len(nums)-2,nums)
res2 = self.robrange(1,len(nums)-1,nums)
return max(res1,res2)
def robrange(self,start,end,nums):
if start == end:
return nums[start]
if end - start == 1:
return max(nums[start],nums[start+1])
dp = [0] * (end-start+1)
dp[0] = nums[start]
dp[1] = max(nums[start],nums[start+1])
for i in range(2,end-start+1):
dp[i] = max(dp[i-1],dp[i-2]+nums[start+i])
return dp[-1]
4、复杂度分析
1️⃣ 时间复杂度:
O
(
N
)
O(N)
O(N)
2️⃣ 空间复杂度:
O
(
N
)
O(N)
O(N)
337. 打家劫舍 III
题目链接:link
1、题目描述
小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为 root 。
除了 root 之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果 两个直接相连的房子在同一天晚上被打劫 ,房屋将自动报警。
给定二叉树的 root 。返回 在不触动警报的情况下 ,小偷能够盗取的最高金额 。
2、思路
1️⃣ 对于每一个节点有两种选择,偷或者不偷
- 偷:dp[1]
- 不偷:dp[0]
2️⃣ 遍历终止条件:空节点,偷不偷都是0,return (0,0)
3️⃣ 后序递归
要想知道两个孩子的结果,才能计算当前偷或者不偷的结果
- 偷当前节点:左右孩子就不能偷了
- d p [ 1 ] = v a l + l e f t d p [ 0 ] + r i g h t d p [ 0 ] dp[1] = val + leftdp[0] + rightdp[0] dp[1]=val+leftdp[0]+rightdp[0]
- 不偷当前节点:左右孩子可偷可不偷,选大的
- d p [ 0 ] = m a x ( l e f t d p [ 0 ] , l e f t d p [ 1 ] ) + m a x ( r i g h t d p [ 0 ] , r i g h t d p [ 1 ] ) dp[0] = max(leftdp[0],leftdp[1]) + max(rightdp[0],rightdp[1]) dp[0]=max(leftdp[0],leftdp[1])+max(rightdp[0],rightdp[1])
3、code
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def rob(self, root: Optional[TreeNode]) -> int:
res = self.travelsal(root)
return max(res[0],res[1])
def travelsal(self,node):
# 返回两个数,分别是不选这个节点的最高金额,和选这个节点的最高金额
if node is None:
return (0,0)
left = self.travelsal(node.left)
right = self.travelsal(node.right)
# 如果不选这个节点,就选左右最大值
res_0 = max(left[0],left[1]) + max(right[0],right[1])
# 如果选这个节点,那么子树肯定是不选的
res_1 = node.val + left[0] + right[0]
return (res_0,res_1)
代码递归顺序:
4、复杂度分析
1️⃣ 时间复杂度:
O
(
N
)
O(N)
O(N),每个节点只遍历了一次
2️⃣ 空间复杂度:
O
(
l
o
g
n
)
O(log n)
O(logn),算上递推系统栈的空间❓