目录
198. 打家劫舍
问题描述
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
示例 1:
输入:[1,2,3,1] 输出:4 解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。 偷窃到的最高金额 = 1 + 3 = 4 。
示例 2:
输入:[2,7,9,3,1] 输出:12 解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。 偷窃到的最高金额 = 2 + 9 + 1 = 12 。
提示:
1 <= nums.length <= 100
0 <= nums[i] <= 400
解题思路
由于受相邻房屋不能访问的约束,假定访问了第1间房屋,就必须跳过第2间,接下来从第3间开始考虑。如果跳过第1间,就可以从第2间开始考虑。
记从房屋k开始到末尾所能得到的最大金额记为f(k),则可以得到以下转移方程:
max()中的第1项表示访问第1间的情况,第2项表示访问第2间的情况。
用递归方法或者迭代方法都可以,这里用递归加memoization来实现。
代码
class Solution:
def rob(self, nums: List[int]) -> int:
N = len(nums)
memo = dict()
def dp(k):
if k in memo:
return memo[k]
if k>(N-1):
return 0
ans = max(nums[k]+dp(k+2), dp(k+1))
memo[k] = ans
return ans
return dp(0)
执行用时:32 ms, 在所有 Python3 提交中击败了88.13%的用户
内存消耗:15.1 MB, 在所有 Python3 提交中击败了5.28%的用户
213. 打家劫舍 II
问题描述
你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。
给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。
示例 1:
输入:nums = [2,3,2] 输出:3 解释:你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。
示例 2:
输入:nums = [1,2,3,1] 输出:4 解释:你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。 偷窃到的最高金额 = 1 + 3 = 4 。
示例 3:
输入:nums = [1,2,3] 输出:3
提示:
1 <= nums.length <= 100
0 <= nums[i] <= 1000
解题思路
由于房子是环成一圈的,所以比上一题多了一点约束。
在上一题中,第1间和最后一间都访问是允许的,但是在本题中则是不允许的(当然,这里是假定从环的某处切开排成线性,否则的话,环本身是没有第1间和最后1间之说的)。
考虑第1间房屋访问与否两种情况:
- case1: 访问房屋1. 剩下的可房屋区间[2,n-2],注意这个区间不再受环形约束,与上一题的基本情况以相同方式处理即可
- case2: 不访问房屋1. 剩下的可房屋区间[1,n-1],这个也不受环形约束,与上一题的基本情况以相同方式处理即可
将上一题的处理封装成一个函数rob(nums),则本问题的解为与之间的最大值。
代码
class Solution:
def rob_linear(self, nums: List[int]) -> int:
N = len(nums)
memo = dict()
def dp(k):
if k in memo:
return memo[k]
if k>(N-1):
return 0
ans = max(nums[k]+dp(k+2), dp(k+1))
memo[k] = ans
return ans
return dp(0)
def rob(self, nums: List[int]) -> int:
return max(nums[0]+self.rob_linear(nums[2:-1]), self.rob_linear(nums[1:]))
执行用时:40 ms, 在所有 Python3 提交中击败了41.06%的用户
内存消耗:15.3 MB, 在所有 Python3 提交中击败了5.19%的用户
740. 删除并获得点数
问题描述
给你一个整数数组 nums
,你可以对它进行一些操作。
每次操作中,选择任意一个 nums[i]
,删除它并获得 nums[i]
的点数。之后,你必须删除 所有 等于 nums[i] - 1
和 nums[i] + 1
的元素。
开始你拥有 0
个点数。返回你能通过这些操作获得的最大点数。
示例 1:
输入:nums = [3,4,2] 输出:6 解释: 删除 4 获得 4 个点数,因此 3 也被删除。 之后,删除 2 获得 2 个点数。总共获得 6 个点数。
示例 2:
输入:nums = [2,2,3,3,3,4] 输出:9 解释: 删除 3 获得 3 个点数,接着要删除两个 2 和 4 。 之后,再次删除 3 获得 3 个点数,再次删除 3 获得 3 个点数。 总共获得 9 个点数。
提示:
1 <= nums.length <= 2 * 10^4
1 <= nums[i] <= 10^4
方法一
思路
由于相同点数的元素要么同时被保留要么同时被删除,所以首先可以将相同值的元素合并(将其值累加),构建一个哈希表,key为其原始值,value为合并后的节点值。
接下来这个问题似乎转变为一个0/1背包问题,但是比基本的0/1背包问题要复杂一些。
考虑某一对{key, value}的取舍。
- 如果保留该key的话,则需要将key-1和key+1从keys集合中删除;
- 如果删除该key的话,可以(但是并不一定,这个还取决于其它的key的取舍)保留key-1和key+1
由此可以得到状态转移方程,但是事实上很难以数学方程的形式表达出来。
代码
from typing import List
class Solution:
def deleteAndEarn(self, nums: List[int]) -> int:
d = dict()
for k,num in enumerate(nums):
if num in d:
d[num] = d[num] + num
else:
d[num] = num
memo = dict()
def dp(d0):
if len(d0)==0:
return 0
if len(d0)==1:
return d0.popitem()[1]
if tuple(d0.keys()) in memo:
return memo[tuple(d0.keys())]
# print(d0)
d1 = d0.copy()
d2 = d0.copy()
k0,v0 = d1.popitem()
# Don't take this key
# print(k0,v0,d1)
v1 = dp(d1)
# Take this key, and hence remove k0+/-1
neibourKey = []
d2.pop(k0)
for key2 in d2:
if key2 == k0+1 or key2 == k0-1:
neibourKey.append(key2)
for key in neibourKey:
# print(key)
d2.pop(key)
# print(d2)
v2 = v0 + dp(d2)
ans = max(v1,v2)
memo[tuple(d0.keys())] = ans
return ans
return dp(d)
if __name__ == "__main__":
sln = Solution()
nums = [3,1]
print(sln.deleteAndEarn(nums))
nums = [3,4,2]
print(sln.deleteAndEarn(nums))
nums = [2,2,3,3,3,4]
print(sln.deleteAndEarn(nums))
超时了。。。
方法二
思路
思路还是同方法一。
但是,不是用哈希表来表达原nums中的元素值及其对应的总和,而是直接用一个数组来表示。原数组下表对应nums原来的元素值,存储的内容则为其对应的总和。这样做的好处在于,不必传递整个哈希表的keys作为memo的key。
按这种方式统计完后,问题编程了从这个数组中的针对每个元素的取舍,约束条件下为不索引相邻的两个元素不能同时取。也就是转变成198相同的问题了。
代码
class Solution:
def deleteAndEarn(self, nums: List[int]) -> int:
total = (max(nums)+1) * [0]
for num in nums:
total[num] = total[num] + num
N = len(total)
memo = dict()
def dp(k):
if k in memo:
return memo[k]
if k>(N-1):
return 0
ans = max(total[k]+dp(k+2), dp(k+1))
memo[k] = ans
return ans
return dp(0)
执行用时:56 ms, 在所有 Python3 提交中击败了24.32%的用户
内存消耗:22.1 MB, 在所有 Python3 提交中击败了5.01%的用户
方法三:排序+动态规划
官解还给出了一个先排序然后再分区进行动态规划的解法,思路如下(代码略):
注意到若 nums 中不存在某个元素 x,则选择任一小于 x 的元素不会影响到大于 x 的元素的选择。因此我们可以将 nums 排序后,将其划分成若干连续子数组,子数组内任意相邻元素之差不超过 1。对每个子数组按照方法二的动态规划过程计算出结果,累加所有结果即为答案。
回到总目录:笨牛慢耕的Leetcode每日一题总目录(动态更新。。。)