0. 贪心基础 0.1 贪心理论入门 在贪心系列开篇词关于贪心算法,你该了解这些! (opens new window) 中,我们就讲解了大家对贪心的普遍疑惑。
什么是贪心算法: 如果找出局部最优并可以推出全局最优,就是贪心,如果局部最优都没找出来,就不是贪心。
贪心算法的套路: 贪心无套路,也没有框架之类的,需要多看多练培养感觉才能想到贪心的思路。
贪心算法的步骤: 贪心算法一般分为如下三步:
将问题分解为若干个子问题
求解每一个子问题的最优解
将局部最优解堆叠成全局最优解
0.2 贪心题目目录 0.2.1 贪心简单题 以下三道题目就是简单题,靠常识。
[分发饼干](# 1. 分发糖果)
[K次取反后最大化的数组和](# 2. K次取反后最大化的数组和)
[柠檬水找零](# 3. 柠檬水找零)
0.2.2 贪心中等题 贪心中等题,靠常识可能就有点想不出来了。 开始初现贪心算法的难度与巧妙之处。
[摆动序列](# 4. 摆动序列)
[单调递增的数字](# 5. 单调递增的数字)
两个维度权衡问题: 在出现两个维度相互影响的情况时 两边一起考虑一定会顾此失彼,要先确定一个维度,再确定另一个一个维度。
[分发糖果](# 6. 分发糖果)
[根据身高重建队列](# 7. 根据身高重建队列)
0.2.3 贪心难题 这里的题目如果没有接触过,其实是很难想到的,甚至接触过,也一时想不出来,所以题目不要做一遍,要多练!
区间问题: 关于区间问题,各种覆盖各种去重
[用最少数量的箭引爆气球](# 8. 用最少数量的箭引爆气球)
[无重叠区间](# 9. 无重叠区间)
[合并区间](# 10. 合并区间)
[划分字母区间](# 11. 划分字母区间)
其他难题:
贪心算法:最大子序和 (opens new window) 其实是动态规划的题目,但贪心性能更优,很多同学也是第一次发现贪心能比动规更优的题目。
贪心算法:加油站 (opens new window) 可能以为是一道模拟题,但就算模拟其实也不简单,需要把while用的很娴熟。但其实是可以使用贪心给时间复杂度降低一个数量级。
最后贪心系列压轴题目贪心算法:我要监控二叉树! (opens new window) 不仅贪心的思路不好想,而且需要对二叉树的操作特别娴熟,这就是典型的交叉类难题了。
1. 分发饼干 https://leetcode-cn.com/problems/assign-cookies/
假设你是一位很棒的家长,想要给你的孩子们一些小饼干。 但是,每个孩子最多只能给一块饼干。
对每个孩子 i
,都有一个胃口值 g[i]
,这是能让孩子们满足胃口的饼干的最小尺寸; 并且每块饼干 j
,都有一个尺寸 s[j]
。 如果 s[j] >= g[i]
,我们可以将这个饼干 j
分配给孩子 i
,这个孩子会得到满足。 你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。
示例 1:
1 2 3 4 5 6 输入: g = [1,2,3], s = [1,1] 输出: 1 解释: 你有三个孩子和两块小饼干,3个孩子的胃口值分别是:1,2,3。 虽然你有两块小饼干,由于他们的尺寸都是1,你只能让胃口值是1的孩子满足。 所以你应该输出1。
示例 2:
1 2 3 4 5 6 输入: g = [1,2], s = [1,2,3] 输出: 2 解释: 你有两个孩子和三块小饼干,2个孩子的胃口值分别是1,2。 你拥有的饼干数量和尺寸都足以让所有孩子满足。 所以你应该输出2.
提示:
1 <= g.length <= 3 * 104
0 <= s.length <= 3 * 104
1 <= g[i], s[j] <= 231 - 1
解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution : def findContentChildren (self, g, s ): if len (s) == 0 or len (g) == 0 : return 0 g = sorted (g) s = sorted (s) output = 0 sP = 0 gP = 0 while gP < len (g) and sP < len (s): if s[sP] >= g[gP]: output += 1 gP += 1 sP += 1 return output
2. K 次取反后最大化的数组和 https://leetcode-cn.com/problems/maximize-sum-of-array-after-k-negations/
给定一个整数数组 A,我们只能 用以下方法修改该数组: 我们选择某个索引 i
并将 A[i]
替换为 -A[i]
,然后总共重复这个过程 K
次。 (我们可以多次选择同一个索引 i
。)
以这种方式修改数组后,返回数组可能的最大和。
示例 1:
1 2 3 输入:A = [4 ,2 ,3 ], K = 1 输出:5 解释:选择索引 (1 ,) ,然后 A 变为 [4 ,-2 ,3 ]。
示例 2:
1 2 3 输入:A = [3 ,-1 ,0 ,2 ], K = 3 输出:6 解释:选择索引 (1 , 2 , 2 ) ,然后 A 变为 [3,1,0,2 ]。
示例 3:
1 2 3 输入:A = [2,-3 ,-1 ,5,-4 ], K = 2 输出:13 解释:选择索引 (1, 4) ,然后 A 变为 [2,3,-1 ,5,4]。
提示:
1 <= A.length <= 10000
1 <= K <= 10000
-100 <= A[i] <= 100
解: 贪心的思路
局部最优: 让绝对值大的负数变为正数,当前数值达到最大
整体最优: 整个数组和达到最大。
局部最优可以推出全局最优。
那么如果将负数都转变为正数了,K依然大于0,此时的问题是: 一个有序正整数序列,如何转变K次正负,让 数组和 达到最大。
那么又是一个贪心:
局部最优: 只找数值最小的正整数进行反转,当前数值可以达到最大(例如正整数数组{5, 3, 1},反转1 得到-1 比 反转5得到的-5 大多了)
全局最优: 整个 数组和 达到最大。
那么本题的解题步骤为:
第一步:将数组按照绝对值大小从大到小排序,注意要按照绝对值的大小
第二步:从前向后遍历,遇到负数将其变为正数,同时K–
第三步:如果K还大于0,那么反复转变数值最小的元素,将K用完
第四步:求和
1 2 3 4 5 6 7 8 9 10 11 12 class Solution : def largestSumAfterKNegations (self, nums, k ): output = 0 nums = sorted (nums, key=abs , reverse=True ) for i in range (len (nums)): if nums[i] < 0 and k != 0 : nums[i] = -nums[i] k -= 1 output += nums[i] if k != 0 and k%2 == 1 : output = output - nums[-1 ]*2 return output
3. 柠檬水找零 https://leetcode-cn.com/problems/lemonade-change/
在柠檬水摊上,每一杯柠檬水的售价为 5
美元。 顾客排队购买你的产品,(按账单 bills
支付的顺序)一次购买一杯。
每位顾客只买一杯柠檬水,然后向你付 5
美元、10
美元或 20
美元。 你必须给每个顾客正确找零,也就是说净交易是每位顾客向你支付 5
美元。
注意,一开始你手头没有任何零钱。
给你一个整数数组 bills
,其中 bills[i]
是第 i
位顾客付的账。如果你能给每位顾客正确找零,返回 true
,否则返回 false
。
示例 1:
1 2 3 4 5 6 7 输入:bills = [5,5,5,10,20] 输出:true 解释: 前 3 位顾客那里,我们按顺序收取 3 张 5 美元的钞票。 第 4 位顾客那里,我们收取一张 10 美元的钞票,并返还 5 美元。 第 5 位顾客那里,我们找还一张 10 美元的钞票和一张 5 美元的钞票。 由于所有客户都得到了正确的找零,所以我们输出 true。
示例 2:
1 2 3 4 5 6 7 输入:bills = [5,5,10,10,20] 输出:false 解释: 前 2 位顾客那里,我们按顺序收取 2 张 5 美元的钞票。 对于接下来的 2 位顾客,我们收取一张 10 美元的钞票,然后返还 5 美元。 对于最后一位顾客,我们无法退回 15 美元,因为我们现在只有两张 10 美元的钞票。 由于不是每位顾客都得到了正确的找零,所以答案是 false。
示例 3:
1 2 输入:bills = [5 ,5 ,10 ] 输出:true
示例 4:
1 2 输入:bills = [10 ,10 ] 输出:false
提示:
1 <= bills.length <= 105
bills[i]
不是 5
就是 10
或是 20
解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution : def lemonadeChange (self, bills ): changeDict = {5 :0 , 10 :0 , 20 :0 } for money in bills: changeDict[money] += 1 if money == 5 : continue if money == 10 : if changeDict[5 ] == 0 : return False changeDict[5 ] -= 1 else : if changeDict[5 ] == 0 : return False if changeDict[10 ] > 0 : changeDict[10 ] -= 1 changeDict[5 ] -= 1 else : if changeDict[5 ] < 3 : return False changeDict[5 ] -= 3 return True
4. 摆动序列 https://leetcode-cn.com/problems/wiggle-subsequence/
如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为 摆动序列 。 第一个差(如果存在的话)可能是正数或负数。 仅有一个元素或者含两个不等元素的序列也视作摆动序列。
例如, [1, 7, 4, 9, 2, 5]
是一个 摆动序列 ,因为差值 (6, -3, 5, -7, 3)
是正负交替出现的。
相反,[1, 4, 7, 2, 5]
和 [1, 7, 4, 5, 5]
不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。
子序列 可以通过从原始序列中删除一些(也可以不删除)元素来获得,剩下的元素保持其原始顺序。
给你一个整数数组 nums
,返回 nums
中作为 摆动序列 的 最长子序列的长度 。
示例 1:
1 2 3 输入:nums = [1,7,4,9,2,5] 输出:6 解释:整个序列均为摆动序列,各元素之间的差值为 (6, -3 , 5, -7 , 3) 。
示例 2:
1 2 3 4 输入:nums = [1,17,5,10,13,15,10,5,16,8] 输出:7 解释:这个序列包含几个长度为 7 摆动序列。 其中一个是 [1, 17, 10, 13, 10, 16, 8] ,各元素之间的差值为 (16, -7 , 3, -3 , 6, -8 ) 。
示例 3:
1 2 输入:nums = [1,2,3,4 ,5,6,7,8 ,9 ] 输出:2
提示:
1 <= nums.length <= 1000
0 <= nums[i] <= 1000
进阶: 你能否用 O(n)
时间复杂度完成此题?
解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution : def wiggleMaxLengthByGreedy (self, nums ): if len (nums) < 2 : return len (nums) MaxLength = 1 state = 0 for i in range (1 ,len (nums)): if nums[i] > nums[i-1 ]: if state != 1 : MaxLength += 1 state = 1 elif nums[i] < nums[i-1 ]: if state != -1 : MaxLength += 1 state = -1 return MaxLengthif __name__ == '__main__' : nums = [3 ,3 ,2 ,5 ] s = Solution() print(s.wiggleMaxLengthByGreedy(nums))
5. 单调递增的数字 https://leetcode-cn.com/problems/monotone-increasing-digits/
给定一个非负整数 N
,找出小于或等于 N
的最大的整数,同时这个整数需要满足其各个位数上的数字是单调递增。
(当且仅当每个相邻位数上的数字 x
和 y
满足 x <= y
时,我们称这个整数是单调递增的。)
示例 1:
示例 2:
示例 3:
说明: N
是在 [0, 10^9]
范围内的一个整数。
解:
贪心算法三步走:
将问题分解为若干个子问题: 把求整个递增的数字这个全局问题,分解为求这个数字每位是什么的子问题
求解每一个子问题的最优解: 将传入数字n每位拆开放进数组numStr 中 倒序 查看 当查看到第 [i] 位时:
若 numStr[i] >= numStr[i-1] : 则仍满足递增 第i位不做修改
若 numStr[i] < numStr[i-1] : 则不满足递增 第[i]位取最大值9(贪心) 第[i-1]位值-1(满足该值 <= n)
将局部最优解堆叠成全局最优解 当 [i] 位发生改变时,要正序向后查看,之前已经求得的局部最优解是否仍然满足递增要求。 需要正序遍历 第 [i] 位 到 最后一位数
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution : def monotoneIncreasingDigits (self, n ): if n < 10 : return n numStr = list (str (n)) for i in range (len (numStr)-1 ,0 ,-1 ): if int (numStr[i]) >= int (numStr[i-1 ]): continue numStr[i] = '9' numStr[i-1 ] = str (int (numStr[i-1 ]) - 1 ) for j in range (i+1 ,len (numStr)): if int (numStr[j]) < int (numStr[j-1 ]): numStr[j] = '9' return int ('' .join(numStr))
6. 分发糖果 https://leetcode-cn.com/problems/candy/
老师想给孩子们分发糖果,有 N 个孩子站成了一条直线,老师会根据每个孩子的表现,预先给他们评分。
你需要按照以下要求,帮助老师给这些孩子分发糖果:
每个孩子至少分配到 1 个糖果。
评分更高的孩子必须比他两侧的邻位孩子获得更多的糖果。
那么这样下来,老师至少需要准备多少颗糖果呢?
示例 1:
1 2 3 输入:[1,0,2] 输出:5 解释:你可以分别给这三个孩子分发 2 、1 、2 颗糖果。
示例 2:
1 2 3 4 输入:[1,2,2] 输出:4 解释:你可以分别给这三个孩子分发 1 、2 、1 颗糖果。 第三个孩子只得到 1 颗糖果,这已满足上述两个条件。
解:
将问题分解为若干个子问题: 设学生 A 和学生 B 左右相邻,A 在 B 左边;
左规则: 当 ratings_B>ratings_A 时,B 的糖比 A 的糖数量多
右规则: 当 ratings_A>ratings_B时,A 的糖比 B 的糖数量多
相邻的学生中,评分高的学生必须获得更多的糖果 等价于 所有学生满足左规则且满足右规则。
求解每一个子问题的最优解:
从左至右遍(正序)历学生成绩 ratings 假设 第[i+1]个学生的成绩比第[i]个更高 则给第[i+1]个学生比第[i]个多一颗糖 否则都只给一颗糖
1 2 3 for i in range (1 ,len (ratings)): if ratings[i] > ratings[i-1 ]: rightOder[i] = rightOder[i-1 ] + 1
从右至左遍(倒序)历学生成绩 ratings 假设 第[j]个学生的成绩比第[j+1]个更高 则给第[j]个学生比第[j+1]个多一颗糖 否则都只给一颗糖
1 2 3 for j in range (len (ratings)-2 ,-1 ,-1 ): if ratings[j] > ratings[j+1 ]: leftOder[j] = leftOder[j+1 ] + 1
将局部最优解堆叠成全局最优解: 最终,取以上 2 轮遍历 leftOder
和 rightOder
对应学生糖果数的 最大值 ,这样则 同时满足左规则和右规则 ,即得到每个同学的最少糖果数量
复杂度分析:
时间复杂度 O(N): 遍历三遍数组即可得到结果
空间复杂度 O(N) 需要借用left,right的线性额外空间
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution : def candy (self, ratings ): leftOder = [1 ] * len (ratings) rightOder = [1 ] * len (ratings) output = 0 for i in range (1 ,len (ratings)): if ratings[i] > ratings[i-1 ]: rightOder[i] = rightOder[i-1 ] + 1 for j in range (len (ratings)-2 ,-1 ,-1 ): if ratings[j] > ratings[j+1 ]: leftOder[j] = leftOder[j+1 ] + 1 for k in range (len (ratings)): output += max (leftOder[k],rightOder[k]) return output
7. 根据身高重建队列 https://leetcode-cn.com/problems/queue-reconstruction-by-height/
假设有打乱顺序的一群人站成一个队列,数组 people
表示队列中一些人的属性(不一定按顺序)。 每个 people[i] = [hi, ki]
表示第 i
个人的身高为 hi
,前面 正好 有 ki
个身高大于或等于 hi
的人。
请你重新构造并返回输入数组 people
所表示的队列。 返回的队列应该格式化为数组 queue
,其中 queue[j] = [hj, kj]
是队列中第 j
个人的属性(queue[0]
是排在队列前面的人)。
示例 1:
1 2 3 4 5 6 7 8 9 10 输入:people = 输出: 解释: 编号为 0 的人身高为 5 ,没有身高更高或者相同的人排在他前面。 编号为 1 的人身高为 7 ,没有身高更高或者相同的人排在他前面。 编号为 2 的人身高为 5 ,有 2 个身高更高或者相同的人排在他前面,即编号为 0 和 1 的人。 编号为 3 的人身高为 6 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。 编号为 4 的人身高为 4 ,有 4 个身高更高或者相同的人排在他前面,即编号为 0、1、2、3 的人。 编号为 5 的人身高为 7 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。 因此 是重新构造后的队列。
示例 2:
提示:
1 <= people.length <= 2000
0 <= hi <= 106
0 <= ki < people.length
题目数据确保队列可以被重建
解:
思路:
先由高到低排序 确定一个贪心维度 people [i][0]
再根据 people [i][1] 来插入数组 确定另一个贪心维度,满足前面 正好 有 ki
个身高大于或等于 hi
的人的要求
复杂度分析:
时间复杂度O(nlogn + n^2)
空间复杂度O(n)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 class Solution : def reconstructQueue (self, people ): self.quickSort(people,0 ,len (people)-1 ) output = list () for i in range (len (people)): if people[i][1 ] > i: output.append(people[i]) output.insert(people[i][1 ] ,people[i]) return output def cmp (self, A, B ): if A[0 ] > B[0 ]: return True elif A[0 ] < B[0 ]: return False else : if A[1 ] > B[1 ]: return False else : return True def quickSort (self, arr, left, right ): if left >= right: return l = left r = right t = arr[l] while l < r: while l < r and not self.cmp(arr[r], t): r -= 1 if l >= r: break arr[l] = arr[r] l += 1 while l < r and self.cmp(arr[l], t): l += 1 if l >= r: break arr[r] = arr[l] r -= 1 arr[l] = t self.quickSort(arr,left,l-1 ) self.quickSort(arr,l+1 ,right)
8. 用最少数量的箭引爆气球 https://leetcode-cn.com/problems/minimum-number-of-arrows-to-burst-balloons/
在二维空间中有许多球形的气球。 对于每个气球,提供的输入是水平方向上,气球直径的开始和结束坐标。 由于它是水平的,所以纵坐标并不重要,因此只要知道开始和结束的横坐标就足够了。 开始坐标总是小于结束坐标。
一支弓箭可以沿着 x 轴从不同点完全垂直地射出。 在坐标 x 处射出一支箭,若有一个气球的直径的开始和结束坐标为 x``start
,x``end
, 且满足 xstart ≤ x ≤ x``end
,则该气球会被引爆。 可以射出的弓箭的数量没有限制。 弓箭一旦被射出之后,可以无限地前进。 我们想找到使得所有气球全部被引爆,所需的弓箭的最小数量。
给你一个数组 points
,其中 points [i] = [xstart,xend]
,返回引爆所有气球所必须射出的最小弓箭数。
示例 1:
1 2 3 输入:points = 输出:2 解释:对于该样例,x = 6 可以射爆 , 两个气球,以及 x = 11 射爆另外两个气球
示例 2:
示例 3:
示例 4:
1 2 输入:points = [[1,2]] 输出:1
示例 5:
1 2 输入:points = [[2,3],[2,3]] 输出:1
提示:
1 <= points.length <= 104
points[i].length == 2
-231 <= xstart < xend <= 231 - 1
解:
1 2 3 4 5 6 7 8 9 10 11 12 class Solution : def findMinArrowShots (self, points ): points.sort(key=lambda x: [x[0 ], x[1 ]]) output = 1 rightBolder = points[0 ][1 ] for left, right in points[1 :]: if left <= rightBolder: rightBolder = min (rightBolder,right) else : output += 1 rightBolder = right return output
9. 无重叠区间 https://leetcode-cn.com/problems/non-overlapping-intervals/
给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。
注意:
可以认为区间的终点总是大于它的起点。
区间 [1,2] 和 [2,3] 的边界相互“接触”,但没有相互重叠。
示例 1:
1 2 3 4 5 输入: 输出: 1 解释: 移除 后,剩下的区间没有重叠。
示例 2:
1 2 3 4 5 输入: 输出: 2 解释: 你需要移除两个 来使剩下的区间没有重叠。
示例 3:
1 2 3 4 5 输入: 输出: 0 解释: 你不需要移除任何区间,因为它们已经是无重叠的了。
解:
1 2 3 4 5 6 7 8 9 10 11 12 class Solution : def eraseOverlapIntervals (self, intervals ): if len (intervals) < 2 : return 0 intervals.sort(key = lambda x:x[1 ]) rightBolder = intervals[0 ][1 ] counter = 1 for left,right in intervals[1 :]: if left >= rightBolder: counter += 1 rightBolder = right return len (intervals) - counter
10. 合并区间 https://leetcode-cn.com/problems/merge-intervals/
以数组 intervals
表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi]
。 请你合并所有重叠的区间,并返回一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间。
示例 1:
1 2 3 输入:intervals = [[1 ,3 ],[2 ,6 ],[8 ,10 ],[15 ,18 ]] 输出:[[1 ,6 ],[8 ,10 ],[15 ,18 ]] 解释:区间 [1 ,3 ] 和 [2 ,6 ] 重叠, 将它们合并为 [1 ,6 ].
示例 2:
1 2 3 输入:intervals = [[1,4],[4,5]] 输出:[[1,5]] 解释:区间 [1 ,4 ] 和 [4 ,5 ] 可被视为重叠区间。
提示:
1 <= intervals.length <= 104
intervals[i].length == 2
0 <= starti <= endi <= 104
解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution : def merge (self, intervals ): if len (intervals) == 0 : return [[]] if len (intervals) == 1 : return intervals intervals.sort(key = lambda x:x[0 ]) output = list () leftBounder = intervals[0 ][0 ] rightBounder = intervals[0 ][1 ] for left,right in intervals[1 :]: if left > rightBounder: output.append([leftBounder,rightBounder]) leftBounder = left leftBounder = min (leftBounder,left) rightBounder = max (rightBounder,right) output.append([leftBounder,rightBounder]) return output
11. 划分字母区间 https://leetcode-cn.com/problems/partition-labels/
字符串 S
由小写字母组成。 我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。 返回一个表示每个字符串片段的长度的列表。
示例:
1 2 3 4 5 6 输入:S = "ababcbacadefegdehijhklij" 输出:[9 ,7 ,8 ] 解释: 划分结果为 "ababcbaca" , "defegde" , "hijhklij" 。 每个字母最多出现在一个片段中。 像 "ababcbacadefegde" , "hijhklij" 的划分是错误的,因为划分的片段数较少。
提示:
S
的长度在[1, 500]
之间。
S
只包含小写字母 'a'
到 'z'
。
解:
先获得每个出现字符的区间,再将重合区间合并,最后剩下的区间数就是答案(同第十题合并区间)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Solution : def partitionLabels (self, s ): if len (s) == 0 : return [[0 ]] if len (s) == 1 : return [[1 ]] charDict = dict () for i in range (len (s)): if s[i] in charDict.keys(): charDict[s[i]][1 ] = i else : charDict[s[i]] = [i,i] charInterval = list () for key in charDict.values(): charInterval.append(key) charInterval.sort(key = lambda x:x[0 ]) leftBoundar = charInterval[0 ][0 ] rightBounder = charInterval[0 ][1 ] output = list () for left,right in charInterval[1 :]: if left > rightBounder: output.append(rightBounder - leftBoundar + 1 ) leftBoundar = left rightBounder = max (rightBounder,right) output.append(rightBounder - leftBoundar + 1 ) return output
12. 最大子序和 https://leetcode-cn.com/problems/maximum-subarray/
给定一个整数数组 nums
,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例 1:
1 2 3 输入:nums = [-2 ,1,-3 ,4,-1 ,2,1,-5 ,4] 输出:6 解释:连续子数组 [4,-1 ,2,1] 的和最大,为 6 。
示例 2:
示例 3:
示例 4:
示例 5:
1 2 输入:nums = [-100000 ] 输出:-100000
提示:
1 <= nums.length <= 3 * 104
-105 <= nums[i] <= 105
解:
局部最优: 当前“连续和”为负数的时候立刻放弃,从下一个元素重新计算“连续和”,因为负数加上下一个元素 “连续和”只会越来越小。
全局最优: 选取最大“连续和”
局部最优的情况下,并记录最大的“连续和”,可以推出全局最优 。
1 2 3 4 5 6 7 8 9 10 11 class Solution : def maxSubArray (self, nums ): result = -float ('inf' ) count = 0 for i in nums: count += i if count > result: result = count if count < 0 : count = 0 return result
13. 加油站 https://leetcode-cn.com/problems/gas-station/
在一条环路上有 N 个加油站,其中第 i 个加油站有汽油 gas[i]
升。
你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i]
升。你从其中的一个加油站出发,开始时油箱为空。
如果你可以绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1。
说明:
如果题目有解,该答案即为唯一答案。
输入数组均为非空数组,且长度相同。
输入数组中的元素均为非负数。
示例 1:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 输入: gas = [1,2,3,4,5] cost = [3,4,5,1,2] 输出: 3 解释: 从 3 号加油站(索引为 3 处)出发,可获得 4 升汽油。此时油箱有 = 0 + 4 = 4 升汽油 开往 4 号加油站,此时油箱有 4 - 1 + 5 = 8 升汽油 开往 0 号加油站,此时油箱有 8 - 2 + 1 = 7 升汽油 开往 1 号加油站,此时油箱有 7 - 3 + 2 = 6 升汽油 开往 2 号加油站,此时油箱有 6 - 4 + 3 = 5 升汽油 开往 3 号加油站,你需要消耗 5 升汽油,正好足够你返回到 3 号加油站。 因此,3 可为起始索引。
示例 2:
1 2 3 4 5 6 7 8 9 10 11 12 13 输入: gas = [2,3,4] cost = [3,4,3] 输出: -1 解释: 你不能从 0 号或 1 号加油站出发,因为没有足够的汽油可以让你行驶到下一个加油站。 我们从 2 号加油站出发,可以获得 4 升汽油。 此时油箱有 = 0 + 4 = 4 升汽油 开往 0 号加油站,此时油箱有 4 - 3 + 2 = 3 升汽油 开往 1 号加油站,此时油箱有 3 - 3 + 3 = 3 升汽油 你无法返回 2 号加油站,因为返程需要消耗 4 升汽油,但是你的油箱只有 3 升汽油。 因此,无论怎样,你都不可能绕环路行驶一周。
1 2 3 4 5 6 7 8 9 10 11 12 class Solution : def canCompleteCircuit (self, gas, cost ): if sum (gas) < sum (cost): return -1 start = 0 curSum = 0 for i in range (len (gas)): curSum += gas[i] - cost[i] if curSum < 0 : curSum = 0 start = i + 1 return start