2019.08.18
53.最大子序和
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6
关键
:令状态 dp[k] 表示以 nums[k] 结尾的连续序列的最大和
状态转移方程
:dp[k] = max(nums[k], dp[k-1]+nums[k])
def maxSubArray(self, nums: List[int]) -> int:dp = [nums[0]]sum_max = nums[0]for k in range(1, len(nums)):temp = max(nums[k], dp[k-1] + nums[k])dp.append(temp)sum_max = max(sum_max, temp)return sum_max
70. 爬楼梯
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
第 i 阶可以由以下两种方法得到:
- 在第 (i−1) 阶后向上爬一阶。
- 在第 (i−2)阶后向上爬 2 阶。
所以到达第 i 阶的方法总数就是到第 (i−1) 阶和第 (i−2) 阶的方法数之和。
关键
:令 dp[i]表示能到达第 i 阶的方法总数
状态转移方程
:dp[i] = dp[i−1] + dp[i−2]
def climbStairs(self, n: int) -> int:f = [1,2]for i in range(2, n):f.append(f[i-1] + f[i-2]) return f[n-1]
746. 使用最小花费爬楼梯
数组的每个索引做为一个阶梯,第 i个阶梯对应着一个非负数的体力花费值 costi。
每当你爬上一个阶梯你都要花费对应的体力花费值,然后你可以选择继续爬一个阶梯或者爬两个阶梯。
您需要找到达到楼层顶部的最低花费。在开始时,你可以选择从索引为 0 或 1 的元素作为初始阶梯。
注意:
cost 的长度将会在 [2, 1000]。
每一个 cost[i] 将会是一个Integer类型,范围为 [0, 999]。
示例 1:
输入: cost = [10, 15, 20]
输出: 15
解释: 最低花费是从cost[1]开始,然后走两步即可到阶梯顶,一共花费15。
示例 2:
输入: cost = [1, 100, 1, 1, 1, 100, 1, 1, 100, 1]
输出: 6
解释: 最低花费方式是从cost[0]开始,逐个经过那些1,跳过cost[3],一共花费6。
class Solution:def minCostClimbingStairs(self, cost: List[int]) -> int:df = [cost[0], cost[1]]for i in range(2, len(cost)):df.append(min(df[i-1], df[i-2]) + cost[i])return min(df[-1], df[-2])
121.买股票的最佳时机
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。
注意 你不能在买入股票前卖出股票。
示例 1:
输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。
示例 2:
输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
def maxProfit(self, prices: List[int]) -> int:if len(prices) == 0:return 0p_min = prices[0]dp = []for p in prices:if p_min <= p:dp.append(p-p_min)else:p_min = preturn max(dp)
303. 区域和检索 - 数组不可变
给定一个整数数组 nums,求出数组从索引 i 到 j (i ≤ j) 范围内元素的总和,包含 i, j 两点。
说明:
你可以假设数组不可变。
会多次调用 sumRange 方法。
示例:
给定 nums = [-2, 0, 3, -5, 2, -1],求和函数为 sumRange()
sumRange(0, 2) -> 1
sumRange(2, 5) -> -1
说明中提到“会多次调用 sumRange 方法”,所以每次都逐个相加计算子区间的和不是理想的做法,
可以使用 dp[i] 存储 nums 前 i 个元素的和,则可得到 i-j 子区间的和为 sum[j] - sum[i-1]。
class NumArray:def __init__(self, nums: List[int]):self.dp = []for i in range(len(nums)):if i == 0:self.dp.append(nums[i])else:self.dp.append(self.dp[i-1] + nums[i])def sumRange(self, i: int, j: int) -> int:if i == 0:return self.dp[j]else:return self.dp[j] - self.dp[i-1]
198. 打家劫舍
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。
示例
输入: [2,7,9,3,1]
输出: 12
解释: 偷窃 1 号房屋 , 偷窃 3 号房屋 ,接着偷窃 5 号房屋。偷窃到的最高金额 = 2 + 9 + 1 = 12 。
可理解为:最大不连续子列和
关键
:令 dp[i] 表示从下标为0的房间盗窃到下标为 i 的房间时可以盗窃的最大金额。
状态转移方程
:dp[i] = max(dp[i-1], dp[i-2] + nums[i])
注意理解:
- 如果 nums[i-1] 被盗,那么 nums[i] 一定不能盗,所以选择 max(dp[i-1], dp[i-2] + nums[i])
- 如果 nums[i-1] 没有被盗,那说明 dp[i-1] = dp[i-2] ,即dp[i-1]中保留了到 i-2 时的最大值,则
nums[i] 一定要被包含进去
def rob(self, nums: List[int]) -> int:l = len(nums)if l == 0:return 0if l == 1:return nums[0]dp = [0] * ldp[0] = nums[0]dp[1] = max(nums[0], nums[1])for i in range(2, l):dp[i] = max(dp[i-1], dp[i-2] + nums[i])return dp[-1]
213. 打家劫舍Ⅱ
你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。
这个地方所有的房屋都围成一圈,这意味着第一个房屋和最后一个房屋是紧挨着的。
同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。
示例 1:
输入: [2,3,2]
输出: 3
解释: 你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。
示例 2:
输入: [1,2,3,1]
输出: 4
解释: 你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
升级版加了限制条件:第一间屋子和最后一间屋子不能同时被抢。即,要么抢第一间,要么抢最后一间。
因此,可以把问题拆分为两个基础版的打家劫舍:
- 去掉第一间,打劫一次
- 去掉最后一间,打劫一次
- 取两次打劫能获得的最大值
class Solution:def rob(self, nums: List[int]) -> int:l = len(nums)if l == 0:return 0if l == 1:return nums[0]if l == 2:return max(nums[0], nums[1])def subrob(nums):length = len(nums)dp = [0] * lengthdp[0] = nums[0]dp[1] = max(nums[0], nums[1]) for i in range(2, length):dp[i] = max(dp[i-1], dp[i-2]+nums[i]) return dp[-1]a = subrob(nums[1:])b = subrob(nums[:-1]) # 切片:左闭右开return max(a,b)