Leetcode刷题笔记——贪心篇
前言
一、贪心 + 双指针
第一题:种花问题
Leetcode605. 种花问题:简单题 (详情点击链接见原题)
假设有一个很长的花坛,一部分地块种植了花,另一部分却没有。可是,花不能种植在相邻的地块上,它们会争夺水源,两者都会死去
python代码解法
class Solution:
def canPlaceFlowers(self, flowerbed: List[int], n: int) -> bool:
index = 0
while index < len(flowerbed) and n > 0:
if flowerbed[index] == 1: # 跳两格(因为相邻格不能种花,所以前一格必然为0)
index += 2
else: # 如果flowerbed[index] == 0 只要考虑后一格是否为 0,为 0 即可种
if index == len(flowerbed) - 1 or flowerbed[index + 1] == 0:
n -= 1
index += 2
else:
index += 3
return n == 0
第二题:盛最多水的容器
Leetcode11. 盛最多水的容器:中等题 (详情点击链接见原题)
给定一个长度为
n
的整数数组height
。有n
条垂线,第i
条线的两个端点是 (i
,0
) 和 (i
,height[i]
)
找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
返回容器可以储存的最大水量
python代码解法
class Solution:
def maxArea(self, height: List[int]) -> int:
max_area = 0
left, right = 0, len(height) - 1
while left < right:
min_h = min(height[left], height[right])
max_area = max(max_area, min_h * (right - left))
if height[left] < height[right]:
left += 1
else:
right -= 1
return max_area
第三题: 救生艇
Leetcode881. 救生艇:中等题 (详情点击链接见原题)
给定数组
people
。people[i]
表示第i
个人的体重 ,船的数量不限,每艘船可以承载的最大重量为limit
。
每艘船最多可同时载两人,但条件是这些人的重量之和最多为limit
解题思路:
一个直观的想法是,由于一个船要么载两人,要么载一人,在人数给定的情况下,为了让使用的总船数最小,要尽可能让更多船载两人,即尽可能多的构造出数量之和不超过 limit
的二元组
先对 people
进行排序,然后使用两个指针 left
和 right
分别从首尾开始进行匹配:
- 如果
people[right] + people[left] > limit
,说明不能成组,由于题目确保人的重量不会超过limit
,此时让people[right]
独立成船,船的数量加1
,right
指针左移 - 如果
people[right] + people[left] <= limit
,说明可以同船,此时船的数量加1
,两个指针往中间靠拢
python代码解法
class Solution:
def numRescueBoats(self, people: List[int], limit: int) -> int:
people.sort()
left, right = 0, len(people) - 1
boat_num = 0
while right >= left:
if people[right] + people[left] > limit:
right -= 1
else:
left += 1
right -= 1
boat_num += 1
return boat_num
第四题:有效三角形的个数
Leetcode611. 有效三角形的个数:中等题 (详情点击链接见原题)
给定一个包含非负整数的数组
nums
,返回其中可以组成三角形三条边的三元组个数
解法1:双指针
首先对数组排序,固定最长的一条边,运用双指针扫描
如果nums[left] + nums[right] <= nums[i]
,left
右移进入下一轮
如果 nums[left] + nums[right] > nums[i]
,同时 nums[left + 1] + nums[right] > nums[i], nums[left + 2] + nums[right] > nums[i]....
说明满足的条件有 right - left
种,right
左移进入下一轮
python代码解法
class Solution:
def triangleNumber(self, nums: List[int]) -> int:
def is_triangle(x, y, z):
if x + y > z:
return True
else:
return False
nums.sort()
ans = 0
for i in range(len(nums) - 1, 1, -1):
left, right = 0, i - 1
while left < right:
if is_triangle(nums[left], nums[right], nums[i]):
ans += right - left # 满足条件的有 right - left 种
right -= 1 # right 左移进入下一轮
else:
left += 1 # left 右移动进入下一轮
return ans
第五题:通过删除字母匹配到字典里最长单词
Leetcode524. 通过删除字母匹配到字典里最长单词:中等题 (详情点击链接见原题)
给你一个字符串
s
和一个字符串数组dictionary
,找出并返回dictionary
中最长的字符串,该字符串可以通过删除s
中的某些字符得到
考点:排序 + 双指针 + 贪心
我们可以先对 dictionary
根据题意进行自定义排序:
- 长度不同的字符串,按照字符串长度排倒序
- 长度相同的则按照字典序排升序
在python
中我们可以通过下面一行代码实现
# dictionary.sort(key=lambda x: (-len(x), x))
dictionary = sorted(dictionary, key=lambda x: (-len(x), x))
然后我们只需对 dictionary
进行顺序查找,找到第一个符合条件的字符串即是答案,使用贪心+双指针实现来进行检查
- 使用两个指针
i
和j
分别扫描s
和dictionary[x]
中的字符 - 当
s[i] != dictionary[x][i]
时,我们让i
右移直到找到s
中第一位与dictionary[x][j]
对得上的位置,然后i
和j
同时右移匹配下一个字符
python代码解法
class Solution:
def findLongestWord(self, s: str, dictionary: List[str]) -> str:
dictionary.sort(key=lambda x: (-len(x), x))
for word in dictionary:
word_index = 0
s_index = 0
while s_index < len(s) and word_index < len(word):
if word[word_index] == s[s_index]:
word_index += 1
s_index += 1
if word_index == len(word):
return word
return ''
第六题:两地调度
Leetcode1029. 两地调度:中等题 (详情点击链接见原题)
公司计划面试
2n
人。给你一个数组costs
,其中costs[i] = [aCosti, bCosti]
。第i
人飞往a
市的费用为aCosti
,飞往b
市的费用为bCosti
python代码解法
class Solution:
def twoCitySchedCost(self, costs: List[List[int]]) -> int:
costs.sort(key=lambda x: (x[0] - x[1]))
# print(costs)
left, right = 0, len(costs) - 1
ans = 0
while left < right:
ans += costs[left][0] + costs[right][1]
left += 1
right -= 1
return ans
if __name__ == '__main__':
s = Solution()
costs = [[10, 20], [30, 200], [400, 50], [30, 20]]
print(s.twoCitySchedCost(costs))
二、贪心 + 哈希表
第一题:受标签影响的最大值
Leetcode1090. 受标签影响的最大值:中等题 (详情点击链接见原题)
我们有一个
n
项的集合。给出两个整数数组values
和labels
,第i
个元素的值和标签分别是values[i]
和labels[i]
。还会给出两个整数numWanted
和useLimit
解题思路
贪心的选择集合中值较大的元素,同时记录每个标签出现的次数,当某个标签出现的次数达到 useLimit
时,我们就不能再选择该标签对应的元素了
具体思路:
- 我们先将集合中的元素按照值从大到小进行排序
- 然后从前往后遍历排序后的元素,在遍历的过程中,我们使用一个哈希表
hash_map
记录每个标签出现的次数,如果某个标签出现的次数达到了useLimt
,我们就跳过该元素,否则就将该元素的值加到最终的答案中,并将标签出现的次数加1
- 当
index
遍历到末尾或者numWanted
已达到要求则跳出循环,返回最终结果
python代码解法
class Solution:
def largestValsFromLabels(self, values: List[int], labels: List[int], numWanted: int, useLimit: int) -> int:
ans = []
for v, l in zip(values, labels):
ans.append((v, l))
ans.sort(key=lambda x: x[0], reverse=True)
res = 0
hash_map = Counter()
index = 0
while index < len(values) and numWanted > 0:
hash_map[ans[index][1]] += 1
if hash_map[ans[index][1]] > useLimit:
index += 1
continue
res += ans[index][0]
numWanted -= 1
index += 1
return res
第二题:划分字母区间
Leetcode763. 划分字母区间:中等题 (详情点击链接见原题)
给你一个字符串
s
。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中
解题思路
- 统计每一个字符最后出现的位置
- 从头遍历字符,并更新字符的最远出现下标,如果找到字符最远出现位置下标和当前下标相等了,则找到了分割点
python代码解法
class Solution:
def partitionLabels(self, s: str) -> List[int]:
hash_map = [0] * 27
for index, value in enumerate(s):
hash_map[ord(value) - ord('a')] = index # 将每一个字母最远出现的下标进行统计
result = []
left, right = 0, 0
for i, v in enumerate(s):
right = max(right, hash_map[ord(v) - ord('a')]) # 从头遍历字符,并更新字符的最远出现下标
if i == right: # 如果找到字符最远出现位置下标和当前下标相等了,则找到了分割点
result.append(right - left + 1)
left = i + 1
return result
第三题:连接两字母单词得到的最长回文串
Leetcode2131:连接两字母单词得到的最长回文串:中等题 (详情点击链接见原题)
给你一个字符串数组
words
。words
中每个元素都是一个包含 两个 小写英文字母的单词
python代码解法
class Solution:
def longestPalindrome(self, words: List[str]) -> int:
count = Counter()
ans = 0
for word in words:
word_reverse = word[::-1]
if word_reverse in count and count[word_reverse] > 0:
ans += 4
count[word_reverse] -= 1
else:
count[word] += 1
for word, value in count.items():
if word[0] == word[1] and value == 1:
ans += 2
break
return ans
三、贪心 + 优先队列
第一题:最低加油次数
Leetcode871. 最低加油次数:困难题 (详情点击链接见原题)
汽车从起点出发驶向目的地,该目的地位于出发位置东面
target
英里处。
沿途有加油站,用数组stations
表示。其中stations[i] = [positioni, fueli]
表示第i
个加油站位于出发位置东面positioni
英里处,并且有fueli
升汽油
解题思路
始终选择油量最大的加油站进行加油,可以确保不存在更优的结果
remian
: 代表当前有多少油(起始remain = startFuel
)
loc
: 代表走了多远
index
: 代表经过的加油站的下标,n
代表加油站总个数
python代码解法
from heapq import heappop, heappush
class Solution:
def minRefuelStops(self, target: int, startFuel: int, stations: List[List[int]]) -> int:
pq = []
n, index = len(stations), 0 # n 表示加油站总个数,idx表示经过的加油站下标
remain, loc, ans = startFuel, 0, 0 # remain表示当前有多少油,loc代表走了多远
while loc < target:
if remain == 0:
if pq:
ans += 1
remain += -heappop(pq) # 每次将remain累加到loc中(使用完剩余的油量可以去到的最远的距离)
else:
return -1
loc, remain = loc + remain, 0 # 更新距离和清空油量
while index < n and stations[index][0] <= loc: # 同时将loc内包含的加油站加入优先队列
heappush(pq, -stations[index][1]) # 取相反数建立小根堆实则等价于用正数建立大根堆
index += 1
return ans
第二题:吃苹果的最大数目
Leetcode1705. 吃苹果的最大数目:中等题 (详情点击链接见原题)
有一棵特殊的苹果树,一连
n
天,每天都可以长出若干个苹果。在第i
天,树上会长出apples[i]
个苹果,这些苹果将会在days[i]
天后(也就是说,第i + days[i]
天时)腐烂,变得无法食用。也可能有那么几天,树上不会长出新的苹果,此时用apples[i] =0
且days[i] = 0
表示
解题思路
直觉上我们会觉得【优先吃掉最快过期的苹果】是最优,而这个维护苹果过期的过程,可以使用小根堆来实现
(最后食用日期,当日产生苹果数量) 的形式存入小根堆进行维护
n
:数组长度, time
:当前时间, ans
:吃到的苹果数量
- 首先,如果
time < n
或者堆不为空,说明还有苹果未被生成或者未被吃掉,继续模拟 - 在当日模拟中,如果
time < n
说明当天有苹果生成,先将苹果以二元组(time + days[time] - 1, apples[time])
的形式加入小根堆中 - 尝试从堆中取出【最后食用日期】,最早【可食用】的苹果,如果堆顶元素已过期,则抛弃
- 如果吃掉
cur
一个苹果后,仍有剩余,并且最后时间大于当前时间(尚未过期),将cur
重新入堆 - 循环上述逻辑直到所有苹果出堆
python代码解法
import heapq
class Solution:
def eatenApples(self, apples: List[int], days: List[int]) -> int:
pq = []
n = len(apples)
time = 0 # 当前时间
ans = 0 # 吃到的苹果数量
while time < n or pq: # 说明还有 苹果未被生成 或者 未被吃掉
while pq and pq[0][0] <= time:
heapq.heappop(pq)
if time < n and apples[time] > 0: # 说明当前有苹果生成(过滤当天没有苹果生成的情况)
heapq.heappush(pq, [time + days[time], apples[time]]) # 以二元组形式(最后食用日期,当日产生苹果的数量)入堆
if pq:
# cur = heapq.heappop(pq)
# if cur[0] > time and cur[1] >= 1:
# cur[1] -= 1
# heapq.heappush(pq, cur)
pq[0][1] -= 1 # 吃掉一个苹果
if pq[0][1] == 0: # 如果没有剩余则出堆
heapq.heappop(pq)
ans += 1
time += 1
return ans
四、区间问题中的贪心思路
第一题:合并区间
Leetcode56:合并区间:中等题 (详情点击链接见原题)
以数组
intervals
表示若干个区间的集合,其中单个区间为intervals[i] = [starti, endi]
。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间
python代码解法
class Solution:
def merge(self, intervals: List[List[int]]) -> List[List[int]]:
# 先按照区间左端点进行从小到大排序
intervals.sort(key=lambda x: x[0]) # 方法1
# intervals = sorted(intervals, key=lambda x: x[0]) # 方法2
merge = []
for interval in intervals:
if not merge or interval[0] > merge[-1][1]: # 如果下一个区间的左端点大于上一个区间的右端点或者merge为空
merge.append(interval)
else: # 如果下一个区间的左端点小于等于上一个区间的右端点,取两个区间右端点较大的那个
merge[-1][1] = max(merge[-1][1], interval[1])
return merge
第二题:删除被覆盖区间
Leetcode1288. 删除被覆盖区间:中等题 (详情点击链接见原题)
给你一个区间列表,请你删除列表中被其他区间所覆盖的区间。
只有当c <= a
且b <= d
时,我们才认为区间[a,b)
被区间[c,d)
覆盖。在完成所有删除操作后,请你返回列表中剩余区间的数目
python代码解法
class Solution:
def removeCoveredIntervals(self, intervals: List[List[int]]) -> int:
intervals = sorted(intervals, key=lambda x: (x[0], -x[1]))
print(intervals)
count = 0
i = 1
temp = intervals[0]
start = temp[0]
end = temp[1]
while i < len(intervals):
if intervals[i][0] >= start and intervals[i][1] <= end:
count += 1
else:
start = max(temp[0], intervals[i][0])
end = max(temp[1], intervals[i][1])
temp = intervals[i]
i += 1
return len(intervals) - count
第三题:用最少数量的箭引爆气球
Leetcode452. 用最少数量的箭引爆气球:中等题 (详情点击链接见原题)
有一些球形气球贴在一堵用
XY
平面表示的墙面上。墙面上的气球记录在整数数组points
,其中points[i] = [xstart, xend]
表示水平直径在xstart
和xend
之间的气球。你不知道气球的确切y
坐标
python代码解法1
class Solution:
def findMinArrowShots(self, points: List[List[int]]) -> int:
points.sort(key=lambda x: x[0]) # 对区间左端点进行从小到大排序
merged = []
for point in points:
if not merged or merged[-1][1] < point[0]:
merged.append(point)
elif merged[-1][1] == point[0]:
merged.pop(-1)
merged.append([point[0], point[0]])
else:
merged[-1][0] = max(merged[-1][0], point[0])
merged[-1][1] = min(merged[-1][1], point[1])
return len(merged)
python代码解法2
第四题:无重叠区间
Leetcode435. 无重叠区间:中等题 (详情点击链接见原题)
给定一个区间的集合
intervals
,其中intervals[i] = [starti, endi]
。返回 需要移除区间的最小数量,使剩余区间互不重叠 。
python代码解法
class Solution:
def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:
intervals = sorted(intervals, key=lambda x: x[0]) # 以左边界排序
# print(intervals)
count = 0 # 用来统计重叠区间的数量
i = 1
end = intervals[0][1]
while i < len(intervals):
if intervals[i][0] >= end: # 无重叠情况
end = intervals[i][1]
elif intervals[i][0] < end: # 重叠情况
end = min(intervals[i][1], end)
count += 1
i += 1
return count
第五题:插入区间
Leetcode57. 插入区间:中等题 (详情点击链接见原题)
给你一个无重叠的 ,按照区间起始端点排序的区间列表,在列表中插入一个新的区间,你需要确保列表中的区间仍然有序且不重叠
python代码解法
class Solution:
def insert(self, intervals: List[List[int]], newInterval: List[int]) -> List[List[int]]:
intervals = sorted(intervals, key=lambda x: x[0])
if not intervals:
intervals.append(newInterval)
i = 0
merged = []
while i < len(intervals) and intervals[i][1] < newInterval[0]:
merged.append(intervals[i])
i += 1
while i < len(intervals) and intervals[i][0] <= newInterval[1]:
newInterval[0] = min(newInterval[0], intervals[i][0])
newInterval[1] = max(newInterval[1], intervals[i][1])
i += 1
merged.append([newInterval[0], newInterval[1]])
while i < len(intervals) and merged[-1][1] < intervals[i][0]:
merged.append(intervals[i])
i += 1
return merged
五、贪心 + 其他
第一题:分发饼干
Leetcode455. 分发饼干:简单题 (详情点击链接见原题)
假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干
对每个孩子i
,都有一个胃口值g[i]
,这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干j
,都有一个尺寸s[j]
。如果s[j] >= g[i]
,我们可以将这个饼干j
分配给孩子i
,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值
解题思路:
这里的局部最优就是大饼干喂给胃口大的,充分利用饼干尺寸喂饱一个
全局最优就是喂饱尽可能多的小孩
python代码解法
class Solution:
def findContentChildren(self, g: List[int], s: List[int]) -> int:
s.sort() # 饼干尺寸排序
g.sort() # 胃口排序
ans = 0
s_p, g_p = len(s) - 1, len(g) - 1
while s_p >= 0 and g_p >= 0:
if s[s_p] >= g[g_p]:
ans += 1
s_p -= 1
g_p -= 1
else:
g_p -= 1
continue
return ans
第二题:柠檬水找零
Leetcode860. 柠檬水找零:简单题 (详情点击链接见原题)
在柠檬水摊上,每一杯柠檬水的售价为
5
美元。顾客排队购买你的产品,(按账单bills
支付的顺序)一次购买一杯。
每位顾客只买一杯柠檬水,然后向你付5
美元、10
美元或20
美元。你必须给每个顾客正确找零,也就是说净交易是每位顾客向你支付5
美元
解题思路:
因为只有三种面额,5
,10
,20
case1
: 顾客给 5
元,直接收下
case2
:顾客给 10
元,消耗一个 5
,增加一个 10
case3
:顾客给 20
,优先消耗一个 10
和一个 5
,如果不够,再消耗三个 5
python代码解法
class Solution:
def lemonadeChange(self, bills: List[int]) -> bool:
i = 0
five, ten, twenty = 0, 0, 0
while i < len(bills):
if bills[i] == 5: # 情形1:顾客给 5 元,直接收下
five += 1
elif bills[i] == 10: # 情形2:顾客给 10 元,消耗一个 5,增加一个 10
if five < 1:
return False
five -= 1
ten += 1
elif bills[i] == 20: # 顾客给 20,优先消耗一个 10 和一个 5,如果不够,再消耗三个 5
if ten >= 1:
if five >= 1:
ten -= 1
five -= 1
else:
return False
else:
if five >= 3:
five -= 3
else:
return False
i += 1
return True
第三题:买卖股票的最佳时机
Leetcode121. 买卖股票的最佳时机:简单题 (详情点击链接见原题)
给定一个数组
prices
,它的第i
个元素prices[i]
表示一支给定股票第i
天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润
由于本题只有一笔交易(买入卖出),遍历一遍数组,计算每次到当天为止的最小股票价格和最大利润
python代码解法
class Solution:
def maxProfit(self, prices: List[int]) -> int:
lowest_price = float('inf') # 最低的买入价格初始化为一个最大值
max_profit = 0 # 最大的收益初始化为 0 (确保不能获取任何利润的情况下返回 0)
for price in prices:
lowest_price = min(lowest_price, price)
max_profit = max(max_profit, price - lowest_price)
return max_profit
第三题进阶:买卖股票的最佳时机 II
Leetcode122. 买卖股票的最佳时机 II:中等题 (详情点击链接见原题)
给你一个整数数组
prices
,其中prices[i]
表示某支股票第i
天的价格。
在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售
解题思路:
加入第 0
天买入,第 3
天卖出,那么利润为 prices[3] - prices[0]
,相当于 (prices[3] - prices[2]) + (prices[2] - prices[1]) + (prices[1] - prices[0])
,此时把利润分解为以每天为单位的维度,而不是从 0
天 到第 3
天整体去考虑
收集正利润的区间,就是股票买卖的区间,而我们只需要关注最终利润,不需要记录区间
局部最优:收集每天的正利润
全局最优:求得最大利润
python代码解法
class Solution:
def maxProfit(self, prices: List[int]) -> int:
profit = []
for i in range(1, len(prices)):
profit.append(prices[i] - prices[i - 1]) # 1.收集每天的利润,组成从第一天开始收获利润的利润序列
ans = 0
for pro in profit:
if pro > 0: # 2.贪心:只收集每天的正利润
ans += pro
return ans
第四题:最大数
Leetcode179: 最大数:中等题 (详情点击链接见原题)
给定一组非负整数
nums
,重新排列每个数的顺序(每个数不可拆分)使之组成一个最大的整数
思路:对于nums
中的任意两个值a
和b
,我们无法从常规角度上确定其大小(先后)关系,但是我们可以根据结果来决定 a
和 b
的排序关系
如果拼接结果 ab
比 ba
好,我们就认为 a
应该放在 b
的前面
3
+ 30
>
30
+ 3
,所以我们认为3
> 30
python代码解法
class Solution:
def largestNumber(self, nums: List[int]) -> str:
def sort_rule(x, y):
a, b = x + y, y + x
if a < b: return 1
elif a > b: return -1
else: return 0
strs = [str(num) for num in nums]
strs.sort(key = cmp_to_key(sort_rule))
if strs[0] == "0":
return "0"
return ''.join(strs)
第五题:最大子数组和
Leetcode53. 最大子数组和:中等题 (详情点击链接见原题)
给你一个整数数组
nums
,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和
如果 -2 1
在一起计算最大子数组和的子数组的起始位置时,一定从1
开始计算,因为负数只会拉低总和
局部最优
:当前连续和为负数的时候立刻放弃,从下一个元素重新计算连续和,因为负数加上下一个元素连续和只会越来越小
全局最优
: 选取最大连续和
python代码解法
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
right = 0
total_sum = 0
max_res = -float('inf')
while right < len(nums):
total_sum += nums[right]
max_res = max(max_res, total_sum)
if total_sum < 0:
total_sum = 0
right += 1
return max_res
第六题:跳跃游戏
Leetcode55. 跳跃游戏:中等题 (详情点击链接见原题)
给你一个非负整数数组
nums
,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度
解题思路
其实跳几步无所谓,关键在于可跳的覆盖范围,不一定非要明确一次究竟跳几步,每次取最大的跳跃步数,这个就是可以跳跃的覆盖范围,在这个范围内不管怎么跳,反正一定可以跳过来
问题转换为跳跃覆盖范围究竟可不可以覆盖到终点
贪心算法局部最优解:每次移动取最大跳跃步数(得到最大覆盖范围)
整体最优解:最后得到整体最大覆盖范围看能否到终点
python代码解法
class Solution:
def canJump(self, nums: List[int]) -> bool:
if len(nums) == 1: # 只有一个元素就能达到
return True
cover = 0
i = 0
while i <= cover: # 注意这里是小于等于cover
cover = max(cover, i + nums[i])
if cover >= len(nums) - 1: # 说明可以覆盖到终点了
return True
i += 1
return False
第六题进阶:跳跃游戏 II
Leetcode45. 跳跃游戏 II:中等题 (详情点击链接见原题)
给定一个长度为
n
的0
索引整数数组nums
。初始位置为nums[0]
总是可以到达数组的最后一个位置,本题求得是跳到终点的最小跳跃次数
错误思路:
局部最优:当前尽可能跳远点,如果还没到终点,跳数 + 1
整体最优:一步尽可能多跳从而达到最少跳数
因为不知道下一跳最远能跳到哪里,所以上述方法是错的
正确思路:
从覆盖范围出发,不管怎么跳,覆盖范围内一定是可以跳到的,以最小的步数增加覆盖范围,覆盖范围一旦覆盖了终点得到的就是最少步数
这里需要统计两个覆盖范围:当前这一步的覆盖范围和下一步的覆盖范围
如果移动下标达到了当前这一步覆盖范围的最远距离,还没有到达终点的话,那么就必须再走一步来增加当前覆盖范围,直到下一步的覆盖范围覆盖了终点
python代码解法
class Solution:
def jump(self, nums: List[int]) -> int:
if len(nums) == 1:
return 0
ans = 0
cur_distance = 0
nex_jump = 0
for i in range(0, len(nums)):
nex_jump = max(nex_jump, i + nums[i]) # 更新下一步覆盖最远距离下标
if i == cur_distance: # 遇到当前覆盖的最远距离下标
ans += 1 # 跳数 + 1
cur_distance = nex_jump # 更新当前覆盖的最远距离下标(相当于加油了)
if nex_jump >= len(nums) - 1: # 当前覆盖最远距到达集合终点,不用做ans++操作了,直接结束
break
return ans
第七题:保持城市天际线
Leetcode807:保持城市天际线:中等题 (详情点击链接见原题)
给你一座由
n x n
个街区组成的城市,每个街区都包含一座立方体建筑。给你一个下标从0
开始的n x n
整数矩阵grid
,其中grid[r][c]
表示坐落于r
行c
列的建筑物的 高度
贪心思路:我们需要确保在调整建筑物的高度后,从水平和竖直两个方向所看到的行和列的最大高度不变
- 我们可以先通过
O(n * m)
的复杂度预处理出grid
中每行的最大值以及每列的最大值 - 在统计答案时,通过判断当前位置
grid[i][j]
与min(row_max[i], col_max[j])
的大小关系来决定当前位置能够增高多少
局部最优:当每个位置都取得最大额度增大高度时
全局最优:整体的增加高度最大
python代码解法
class Solution:
def maxIncreaseKeepingSkyline(self, grid: List[List[int]]) -> int:
row, col = len(grid), len(grid[0])
ans = 0
row_max, col_max = [], []
for i in range(row):
r_max = 0
c_max = 0
for j in range(col):
r_max = max(r_max, grid[i][j])
c_max = max(c_max, grid[j][i])
row_max.append(r_max)
col_max.append(c_max)
for x in range(row):
for y in range(col):
ans += min(row_max[x], col_max[y]) - grid[x][y]
return ans
第八题:加油站
Leetcode134. 加油站:中等题 (详情点击链接见原题)
在一条环路上有
n
个加油站,其中第i
个加油站有汽油gas[i]
升
python代码解法
class Solution:
def canCompleteCircuit(self, gas: List[int], cost: List[int]) -> int:
totalSum, curSum = 0, 0
start = 0
for i in range(len(gas)):
curSum += (gas[i] - cost[i])
totalSum += (gas[i] - cost[i])
if curSum < 0:
start = i + 1 # 起始位置更新为i+1
curSum = 0 # curSum从0开始
if totalSum < 0: # 说明怎么走都不可能跑完一圈了
return -1
return start
第九题:森林中的兔子
Leetcode781. 森林中的兔子:中等题 (详情点击链接见原题)
森林中有未知数量的兔子。提问其中若干只兔子 “还有多少只兔子与你(指被提问的兔子)颜色相同?” ,将答案收集到一个整数数组
answers
中,其中answers[i]
是第i
只兔子的回答
结论:
1.同一颜色的兔子回答的数值必然是一样的
2.但回答同样数值的不一定就是同颜色的兔子
要我们求最少的兔子数量,不妨假设某种颜色的兔子有 m
只,它们回答的答案数值为 cnt
,那么 m
和 cnt
满足什么关系?
m = cnt + 1
如果在answer数组里,回答 有 cnt
的数量的兔子颜色与我相同的兔子数量为 t
的话,分情况讨论
t ≤ cnt + 1
:为达到最少的兔子数量,我们可以假定这 t 只兔子为同一颜色【既能满足题意,又不会导致额外的兔子数量增加】
t > cnt + 1
:我们知道回答数量为 cnt
的兔子应该有 cnt + 1
只,这时候说明有数量相同的不同颜色的兔子进行了回答,为达到最少的兔子数量,我们应当将 t
分为若干种颜色,并尽可能让某一种颜色的兔子为 cnt + 1
只,这样能够满足题意
换句话说
局部最优:我们应该让【同一颜色的兔子数量】尽量多
全局最优:实现整体【总的兔子数量】最少
python代码解法
class Solution:
def numRabbits(self, answers: List[int]) -> int:
answers.sort()
i, ans = 0, 0
while i < len(answers):
cur = answers[i]
ans += (cur + 1)
while cur > 0 and i + 1 < len(answers) and answers[i] == answers[i + 1]:
cur -= 1
i += 1
i += 1
return ans
第十题:根据身高重建队列
Leetcode406. 根据身高重建队列:中等题 (详情点击链接见原题)
假设有打乱顺序的一群人站成一个队列,数组
people
表示队列中一些人的属性(不一定按顺序)。每个people[i] = [hi, ki]
表示第i
个人的身高为hi
,前面 正好 有ki
个身高大于或等于hi
的人
解题思路
身高从大到小排(身高相同k
小的站前面)
{7 0} {7 1} {6 1} {5 0} {5 2} {4 4}
按照身高排序后,优先按身高高的people
的k
来插入,后面插入节点也不会影响前面已经插入的节点,最终按照k
的规则完成了队列
第十一题:单调递增的数字
Leetcode738. 单调递增的数字:中等题 (详情点击链接见原题)
当且仅当每个相邻位数上的数字
x
和y
满足x <= y
时,我们称这个整数是单调递增的。
给定一个整数n
,返回 小于或等于n
的最大数字,且数字呈 单调递增
python代码解法(暴力解法)
class Solution:
def check(self, n):
while n:
last = n % 10
n //= 10
if last < n % 10:
return False
return True
def monotoneIncreasingDigits(self, n: int) -> int:
if self.check(n):
return n
else:
while n > 0:
n -= 1
if self.check(n):
return n
解题思路:
python代码解法(优化解法)
class Solution:
def monotoneIncreasingDigits(self, n: int) -> int:
st = list(str(n)) # 1.将整数转换为字符串
flag = len(st) # 2. flag 用来标记赋值 9 从哪里开始
for i in range(len(st) - 1, 0, -1):
if st[i - 1] > st[i]: # 3.如果当前字符比前一个字符小说明需要修改前一个字符
st[i - 1] = str(int(st[i - 1]) - 1) # 4.将前一个字符减1
flag = i
for i in range(flag, len(st)): # 5.将修改位置后面的字符都设置为9
st[i] = '9'
return int(''.join(st)) # 6.将列表转换为字符串,并将字符串转换为整数并返回
第十二题:分发糖果
Leetcode135. 分发糖果:困难题 (详情点击链接见原题)
n
个孩子站成一排。给你一个整数数组ratings
表示每个孩子的评分。
你需要按照以下要求,给这些孩子分发糖果:每个孩子至少分配到1
个糖果。
1.相邻两个孩子评分更高的孩子会获得更多的糖果。
2.请你给每个孩子分发糖果,计算并返回需要准备的 最少糖果数目
解题思路:
先确定一边,再确定另一边,例如比较每一个孩子的左边,然后再比较右边
先确定右边评分大于左边的情况(从前向后遍历)
局部最优:只要右边评分比左边大,右边的孩子就多一个糖果
全局最优:相邻的孩子中,评分高的右孩子获得比左边孩子更多的糖果
接下来:确定左孩子大于右孩子的情况一定要从后向前遍历
局部最优:如果 rating[i] > rating[i + 1]
,此时 candy[i]
就有两个选择了,取 candy[i + 1]
和 candy[i]
中最大的糖果数量,保证第 i
个小孩的糖果数量大于左右两边的
全局最优:相邻的孩子中,评分高的孩子获得更多的糖果
python代码解法
class Solution:
def candy(self, ratings: List[int]) -> int:
n = len(ratings)
candy = [1] * n # 1.初始化糖果数组,保证每个小孩至少分到1个糖果
for i in range(n): # 2.从前向后遍历
if i > 0 and ratings[i] > ratings[i - 1]:
candy[i] = candy[i - 1] + 1
for j in range(n - 1, -1, -1): # 3.从后向前遍历
if j > 0 and ratings[j - 1] > ratings[j]:
candy[j - 1] = max(candy[j - 1], candy[j] + 1)
return sum(candy)
第十四题:最小操作次数使数组元素相等 II
Leetcode462. 最小操作次数使数组元素相等 II:中等题 (详情点击链接见原题)
给你一个长度为
n
的整数数组nums
,返回使所有数组元素相等需要的最小操作数
python代码解法
class Solution:
def minMoves2(self, nums: List[int]) -> int:
left, right = 0, len(nums) - 1
mid = (left + right) // 2
nums.sort()
ans = 0
for num in nums:
ans += abs(num - nums[mid])
return ans
总结
本文给大家介绍了贪心算法在面试种的常见高频考点