leetcode_打家劫舍问题
打家劫舍1
题目:你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。
分析:
dp[i]定义:nums[0,i]的最多的金额集合;
状态转移方程:dp[i] = max(dp[i-1],dp[i-2]+nums[i])
base case: dp[0] = i,dp[1] =max(dp[0],dp[1])
def rob(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
n = len(nums)
if n == 1 :
return nums[0]
if n==0: return 0
dp = [val for val in nums]
if dp[0] >dp[1]:
dp[1] = dp[0]
for i in range(2,n):
dp[i] = max(dp[i-2]+nums[i],dp[i],dp[i-1])
return dp[-1]
打家劫舍2
题目:你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都围成一圈,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。
**分析:**相比打家劫舍1 ,增加了一个限制条件,就是,首尾元素不能同时出现。
DP 问题:
dp[i]定义:nums[0,i]的最多的金额集合;
分两种情况:1.带首元素的最优集合,2.带尾元素的最优集合;3.首尾元素都不带的最优集合。
状态转移方程:dp[i] = max(dp[i-1],dp[i-2]+nums[i])
base case: dp[0] = i,dp[1] =max(dp[0],dp[1])
‘’’
def rob_range(self,arr,left,right):
arr = arr[left:right]
n = len(arr)
if n==0:
return 0
if n==1:
return arr[0]
dp = [val for val in arr]
if dp[0] > dp[1]:
dp[1] = dp[0]
for i in range(2,n):
dp[i] = max(dp[i-2]+arr[i],dp[i],dp[i-1])
return dp[-1]
def rob(self,nums):
n = len(nums)
if n == 1 :
return nums[0]
if n == 0 :
return 0
res1 = self.rob_range(nums,1,n-1) # 第一家和最后一家都不偷
res2= self.rob_range(nums,1,n) #第一家不偷
res3 = self.rob_range(nums,0,n-1) #最后一家不偷
return max([res1,res2,res3])
打家劫舍 3 (二叉树)
题目:在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。
计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。
解法一:递归实现,重复子问题,先找到最小规模的问题,就是最优子结构;
使用爷爷-两个孩子-4个孙子 小偷家族来说明问题,这个家族的每个成员偷到的钱就是对应二叉树结点的值。
问题的目标就是在这棵二叉树能偷到最多的钱。
可分为两种情况:4个孙子偷的钱 + 爷爷的钱 VS 两个儿子偷的钱
method1 = root.val + rob(root.left.left) + rob(root.left.right) +rob(root.right.left) + rob(root.right.right)
method2 = rob(root.left) + root(root.right)
时间复杂度:O(n*n)
def robtree(root):
dic = dict()
return rob(root,dic)
def rob(root,dic):
if root ==None:
return 0
if root in dic.keys():
return dic[root]
res = root.val
if root.left:
res += rob(root.left.left)+rob(root.left.right)
if root.right:
res += rob(root.right.left) + rob(root.right.right)
return max(res,rob(root.left)+rob(root.right))
leetcode python 超出时间限制
解法2:使用DP table-备忘录
public int rob(TreeNode root) {
HashMap<TreeNode, Integer> memo = new HashMap<>();
return robInternal(root, memo);
}
public int robInternal(TreeNode root, HashMap<TreeNode, Integer> memo) {
if (root == null) return 0;
if (memo.containsKey(root)) return memo.get(root);
int money = root.val;
if (root.left != null) {
money += (robInternal(root.left.left, memo) + robInternal(root.left.right, memo));
}
if (root.right != null) {
money += (robInternal(root.right.left, memo) + robInternal(root.right.right, memo));
}
int result = Math.max(money, robInternal(root.left, memo) + robInternal(root.right, memo));
memo.put(root, result);
return result;
}
解法三
分析:不用备忘录,我们使用一个大小为2的数组来表示 int[] res = new int[2] 0代表不偷,1代表偷。
换一种方法定义问题,每个节点可以选择偷偷或者不偷两种状态,根据题目意思,相邻不能偷,所以,当前节点选择偷时,那么两个孩子节点就不能选择偷了;当前节点选择不偷时,两个孩子节点只需要拿最多的钱出来就行(两个孩子节点偷不偷没关系)
单个节点状态转移方程如下:
root[0] = Math.max(rob(root.left)[0], rob(root.left)[1]) + Math.max(rob(root.right)[0], rob(root.right)[1])
root[1] = rob(root.left)[0] + rob(root.right)[0] + root.val;
def rob(root):
res = robtree(root)
return max(res)
def robtree(root):
if root == None:
return [0,0]
left = robtree(left)
right = robtree(right)
rob = left[0] +right[0] + root.val
not_rob = max(left[0],left[1])+max(right[0],right[1])
return [not_rob,rob]
现在偷家都要讲究算法了,哈哈哈!