1005. K次取反后最大化的数组和
本题非常简单,但如果要以贪心的思想高效解题,还要分成两种情况思考:
- 如果数组中有负数
- 局部最优:优先反转绝对值最大的负数
- 如果数组中全部非负,此时还需要翻转
- 局部最优:优先反转最小的非负数
这样的解法复杂度非常低,堪称最优解。
本题最重要的是保持贪心的思想来解题,即使题目很简单,但需要分类的贪心尚且是第一次遇到,要想清楚局部最优再开始解决贪心问题,否则就无法获得贪心的锻炼。
class Solution:
def largestSumAfterKNegations(self, nums: List[int], k: int) -> int:
nums.sort(key=lambda x:abs(x), reverse=True)
for i in range(len(nums)):
if nums[i] < 0 and k > 0:
nums[i] *= -1
k -= 1
if k > 0 and k % 2 == 1:
nums[-1] *= -1
return sum(nums)
直接的解法
每一次取反,都找到当前的最小值然后进行取反,进行 k 次即可。
但这么写的复杂度很高,每次都需要遍历整个数组,不推荐,而且似乎没有体现出贪心的思想。
class Solution:
def findMinIdx(self, nums: List[int]) -> int:
min_idx = -1
min_value = float('inf')
for i in range(len(nums)):
if nums[i] < min_value:
min_idx = i
min_value = nums[i]
return min_idx
def largestSumAfterKNegations(self, nums: List[int], k: int) -> int:
for _ in range(k):
curr_min_idx = self.findMinIdx(nums)
nums[curr_min_idx] *= -1
return sum(nums)
134. 加油站
如果 总油量 - 总消耗 = 总剩余油量
≥
\geq
≥ 0,那就一定能找到一个起始位置能成功环绕一圈。
那么从头开始,记录 [0, curr_pos]
区间内的总剩余油量。一旦总剩余油量变为负数,意味着从 [0, curr_pos]
中的任一位置出发都会面临断油,所以将 curr_pos + 1
作为新的区间起点,重新记录区间内的总剩余油量。
一旦总剩余油量变为负数,意味着从 [0, curr_pos]
中的任一位置出发都会面临断油可以从下图中得到验证:
局部最优:当前的区间剩余油量若是负数,则从其中任意位置出发都会断油,只有从后面的位置出发才有可能成功环绕。
全局最优:找到起始位置。
- 时间复杂度: O ( n ) O(n) O(n)
- 空间复杂度: O ( 1 ) O(1) O(1)
class Solution:
def canCompleteCircuit(self, gas: List[int], cost: List[int]) -> int:
if sum(gas) < sum(cost):
return -1
curr_rest = 0
curr_start = 0
for i in range(len(gas)):
curr_rest += gas[i] - cost[i]
if curr_rest < 0:
curr_rest = 0
curr_start = i + 1
return curr_start
暴力算法
复杂度为
O
(
n
2
)
O(n^2)
O(n2) 的暴力算法,提交的话会超时。
暴力算法的写法比较 tricky,模拟一圈非常依赖 % 以及 while loop 的使用,要想写对也需要很正确的思路。
class Solution:
def canCompleteCircuit(self, gas: List[int], cost: List[int]) -> int:
for start in range(len(gas)):
curr_gas = gas[start] - cost[start]
curr_pos = (start + 1) % len(gas)
while curr_gas > 0 and curr_pos != start:
curr_gas += gas[curr_pos] - cost[curr_pos]
curr_pos = (curr_pos + 1) % len(gas)
if curr_gas >= 0 and curr_pos == start:
return start
return -1
135. 分发糖果
本题要求每个孩子至少分配到 1 个糖果和相邻的孩子中,评分高的孩子必须获得更多的糖果,其中第二个要求需要从左向右和从右向左的比较均满足条件。
重点:先确定一边,再确定另一边。两个方向一起考虑只会顾此失彼。
这也代表着需要两个情况下的局部最优。
-
右孩子的评分大于左孩子:
- 局部最优为,如果右边的评分比左边的高,则右边就比左边多一个糖果。
- 全局最优为,相邻的孩子中,评分高的右孩子比左孩子的糖果更多。
- 此时需要从左向右遍历,因为这个局部最优是依赖左侧已经发生的更新。
-
左孩子的评分大于右孩子:
- 局部最优为,如果左边的评分比右边的高,则左边就比右边多一个糖果,同时也要保证之前维持的全局最优相邻的孩子中,评分高的右孩子比左孩子的糖果更多。
- 全局最优为,相邻的孩子中,评分高的孩子获得更多的糖果。
- 此时需要从右向左遍历,因为这个局部最优依赖于右侧已经发生的更新。
class Solution:
def candy(self, ratings: List[int]) -> int:
candies = [1] * len(ratings)
for i in range(1, len(ratings)):
if ratings[i] > ratings[i-1]:
candies[i] = candies[i-1] + 1
for j in range(len(ratings) - 2, -1, -1):
if ratings[j] > ratings[j+1]:
candies[j] = max(candies[j], candies[j+1] + 1)
return sum(candies)
重点:有双向要求,就要拆成双向贪心!要分别考虑不同的局部最优,分步实现贪心算法!