基础知识补充
贪心算法:在对问题求解时,总是做到局部最优;
但局部最优并不总能获得整体最优解,但通常能获得近似最优解
题目
121 买卖股票的最佳时机
给定一个数组 prices
,它的第 i
个元素 prices[i]
表示一支给定股票第 i
天的价格。你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0
。
思路:如果day2价格高于day1价格,就假设day1买入day2卖出计算收益,如果day3价格高于day2价格,得假设day1买入day2不处理day3卖出...即需要记录前置的历史最低价,出现更低的价格时更新,而出现更高的价格时计算收益,出现次高价格的收益低于最高收益,不会被记录;
class Solution(object):def maxProfit(self, prices):""":type prices: List[int]:rtype: int"""buy, sell = prices[0], 0for val in prices:if val > buy: sell = max(val-buy, sell)else: buy = valreturn sell
55 跳跃游戏
给你一个非负整数数组 nums
,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度判断你是否能够到达最后一个下标,如果可以,返回 true
;否则,返回 false
。
思路:当前坐标元素大于数组长度时,一定能到最后一个下标;否则每一步都跳满,再次判断;若当前坐标元素为0,就选择回退一步,再跳满,否则回退两步再跳满...回退到开头仍无法跳过该处0,则永远不可能到达最后一个下标;
class Solution(object):def canJump(self, nums):""":type nums: List[int]:rtype: bool"""i, n = 0, len(nums)-1while i>=0 and i<n:if nums[i]: i = i+nums[i]else:idx = iwhile idx >= 0:if idx+nums[idx]>i: breakidx -= 1i = idxreturn i>=0
更简洁思路:一次遍历每个元素(除最后),用变量记录当前能到达的最远位置,下标大于最远距离时遍历结束,当遍历结束时判断最远位置是否到达最后一个坐标即可;
class Solution(object):def canJump(self, nums):""":type nums: List[int]:rtype: bool"""distance, n = 0, len(nums)-1for i, val in enumerate(nums):if i > distance: breakdistance = max(distance, i+val)return distance>=n
45 跳跃游戏II
给定一个长度为 n
的 0 索引整数数组 nums
。初始位置为 nums[0]
。
每个元素 nums[i]
表示从索引 i
向后跳转的最大长度。换句话说,如果你在 下标i
处,你可以跳转到任意 下标i+j
处:
0 <= j <= nums[i]
i + j < n
返回到达 nums[n - 1]
的最小跳跃次数。生成的测试用例可以到达 nums[n - 1]
思路:动态规划
dp[i] 表示到达下标 i 的最小跳跃数
转移状态:dp[i] = min(dp[i], dp[k]+1) k+nums[k]>=i
初始状态:dp[0] = 0
class Solution(object):def jump(self, nums):""":type nums: List[int]:rtype: int"""n = len(nums)dp = [float('inf')]*ndp[0] = 0for i in range(n):for k in range(i):if k+nums[k] >= i:dp[i] = min(dp[i], dp[k]+1)return dp[-1] # 执行用时过长
动态规划的优化:
dp[i] 是一个非降序数组,所以在找dp[k]时只要找到第一个符合条件的k即可;
而且对于递增的 i,k也是递增的,不需要回退到0再遍历;
class Solution(object):def jump(self, nums):""":type nums: List[int]:rtype: int"""n = len(nums)dp = [float('inf')]*ndp[0] = 0k = 0for i in range(1, n):while k+nums[k] < i:k+=1dp[i] = dp[k]+1 # 由于会进行这个+1操作,要么dp[0]=-1,要么遍历跳过nums[0]return dp[-1]
更简洁思路:每次跳跃都要跳到最远
以 [3,1,4,2,8] 为例:3最远可以跳到2,在3-2的范围里搜索下一次能跳跃的最远值;
仍依次遍历数组(除最后),当运动到前一次跳跃的最远值时(边界2),将跳跃次数+1同时更新边界;
class Solution(object):def jump(self, nums):""":type nums: List[int]:rtype: int"""n = len(nums)distance, cnt, end = 0, 0, 0for i in range(n-1):distance = max(distance, i+nums[i])if i == end:cnt+=1end = distancereturn cnt
763 划分字母区间
给你一个字符串 s
。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。注意,划分结果需要满足:将所有划分结果按顺序连接,得到的字符串仍然是 s
。返回一个表示每个字符串片段的长度的列表。
思路:片段数尽可能多等于每个片段尽可能短;
对于第一个字符(左界),需要将其最远的同字符(右界)与其共同在一个片段中,然后界内判断还需不需要纳入其他字符的同字符(改变右界)当左右界重合时得到了符合要求的一个最短片段;
因为需要查找同字符的最远位置,事先构造了一个哈希表储存字符和其对应下标;
优化:只保存同字符的最远坐标,可以节省哈希表的空间;
class Solution(object):def partitionLabels(self, s):""":type s: str:rtype: List[int]"""# tmp = []tmp = defaultdict(list)for i in range(len(s)): # tmp[s[i]] = itmp[s[i]].append(i)left, right, out = 0,0,[]for i in range(len(s)):right = max(right, max(tmp[s[i]]))if i == right: out.append(right-left+1)left, right = i+1, 0return out