134. 加油站
【暴力法】 for循环适合模拟从头到尾的遍历,而while循环适合模拟环形遍历,要善于使用while!
class Solution:
def canCompleteCircuit(self, gas: List[int], cost: List[int]) -> int:
for i in range(len(cost)):
rest = gas[i] - cost[i] # 记录剩余油量
index = (i + 1) % len(cost) # 下一个加油站的索引
while rest > 0 and index != i: # 模拟以i为起点行驶一圈(如果有rest==0,那么答案就不唯一了)
rest += gas[index] - cost[index] # 更新剩余油量
index = (index + 1) % len(cost) # 更新下一个加油站的索引
if rest >= 0 and index == i: # 如果以i为起点跑一圈,剩余油量>=0,并且回到起始位置
return i # 返回起始位置i
return -1 # 所有起始位置都无法环绕一圈,返回-1
贪心法版本一【优先掌握这个方法】
那么局部最优:当前累加rest[i]的和curSum一旦小于0,起始位置至少要是i+1,因为从i之前开始一定不行。全局最优:找到可以跑一圈的起始位置。
class Solution:
def canCompleteCircuit(self, gas: List[int], cost: List[int]) -> int:
curSum = 0 # 当前累计的剩余油量
totalSum = 0 # 总剩余油量
start = 0 # 起始位置
for i in range(len(gas)):
curSum += gas[i] - cost[i]
totalSum += gas[i] - cost[i]
if curSum < 0: # 当前累计剩余油量curSum小于0
start = i + 1 # 起始位置更新为i+1
curSum = 0 # curSum重新从0开始累计
if totalSum < 0:
return -1 # 总剩余油量totalSum小于0,说明无法环绕一圈
return start
贪心法【版本二】
class Solution:
def canCompleteCircuit(self, gas: List[int], cost: List[int]) -> int:
curSum = 0 # 当前累计的剩余油量
minFuel = float('inf') # 从起点出发,油箱里的油量最小值
for i in range(len(gas)):
rest = gas[i] - cost[i]
curSum += rest
if curSum < minFuel:
minFuel = curSum
if curSum < 0:
return -1 # 情况1:整个行程的总消耗大于总供给,无法完成一圈
if minFuel >= 0:
return 0 # 情况2:从起点出发到任何一个加油站时油箱的剩余油量都不会小于0,可以从起点出发完成一圈
for i in range(len(gas) - 1, -1, -1):
rest = gas[i] - cost[i]
minFuel += rest
if minFuel >= 0:
return i # 情况3:找到一个位置使得从该位置出发油箱的剩余油量不会小于0,返回该位置的索引
return -1 # 无法完成一圈
135. 分发糖果
力扣链接
这道题目一定是要确定一边之后,再确定另一边,例如比较每一个孩子的左边,然后再比较右边,如果两边一起考虑一定会顾此失彼。
先确定右边评分大于左边的情况(也就是从前向后遍历)
局部最优:只要右边评分比左边大,右边的孩子就多一个糖果
全局最优:相邻的孩子中,评分高的右孩子获得比左边孩子更多的糖果
再确定左孩子大于右孩子的情况==(从后向前遍历)==【会用到前面的结果】
这在leetcode上是一道困难的题目,其难点就在于贪心的策略,如果在考虑局部的时候想两边兼顾,就会顾此失彼。
那么本题我采用了两次贪心的策略:
一次是从左到右遍历,只比较右边孩子评分比左边大的情况。
一次是从右到左遍历,只比较左边孩子评分比右边大的情况。
这样从局部最优推出了全局最优,即:相邻的孩子中,评分高的孩子获得更多的糖果
时间复杂度: O(n) 空间复杂度: O(n)
class Solution:
def candy(self, ratings: List[int]) -> int:
candyVec = [1] * len(ratings)
# 从前向后遍历,处理右侧比左侧评分高的情况
for i in range(1, len(ratings)):
if ratings[i] > ratings[i - 1]:
candyVec[i] = candyVec[i - 1] + 1
# 从后向前遍历,处理左侧比右侧评分高的情况
for i in range(len(ratings) - 2, -1, -1):
if ratings[i] > ratings[i + 1]:
candyVec[i] = max(candyVec[i], candyVec[i + 1] + 1)
# 统计结果
result = sum(candyVec)
return result
860.柠檬水找零
要先用10和5找零钱,再用5块*3
所以局部最优:遇到账单20,优先消耗美元10,完成本次找零。全局最优:完成全部账单的找零。
class Solution:
def lemonadeChange(self, bills: List[int]) -> bool:
five = 0
ten = 0
for bill in bills: # 情况一:收到5美元
if bill == 5:
five += 1
if bill == 10: # 情况二:收到10美元
if five <= 0:
return False
ten += 1
five -= 1
# 情况三:收到20美元【 # 先尝试使用10美元和5美元找零,# 如果无法使用10美元找零,则尝试使用三张5美元找零】
if bill == 20:
if five > 0 and ten > 0:
five -= 1
ten -= 1
elif five >= 3:
five -= 3
else:
return False
return True
406.根据身高重建队列
力扣链接
遇到两个维度权衡的时候,一定要先确定一个维度,再确定另一个维度。
先按照身高,从高到低排序
局部最优:优先按身高高的people的k来插入。插入操作过后的people满足队列属性
全局最优:最后都做完插入操作,整个队列满足题目队列属性
class Solution:
def reconstructQueue(self, people: List[List[int]]) -> List[List[int]]:
# 先按照h维度的身高顺序从高到低排序。确定第一个维度
# lambda返回的是一个元组:当-x[0](维度h)相同时,再根据x[1](维度k)从小到大排序
people.sort(key=lambda x: (-x[0], x[1]))
que = []
# 根据每个元素的第二个维度k,贪心算法,进行插入
# people已经排序过了:同一高度时k值小的排前面。
for p in people:
que.insert(p[1], p) #把p插入到位置p[1]
return que