1005. K 次取反后最大化的数组和 - 力扣(LeetCode)
这部分的代码对于各种边界条件调试了挺久才搞定,代码如下
python
class Solution:
def largestSumAfterKNegations(self, nums: List[int], k: int) -> int:
if len(nums) == 0:
return nums
nums = sorted(nums)
for i in range(k):
if i < len(nums) and nums[i] < 0:
nums[i] = -nums[i]
else:
if i >= len(nums):
if (k-i) % 2 == 1:
nums[-1] = -nums[-1]
break
elif (k-i) % 2 == 1:
if i > 0 and nums[i-1] < nums[i]:
nums[i-1] = -nums[i-1]
else:
nums[i] = -nums[i]
break
print(nums)
return sum(nums)
下面是GPT4给出的题解,比我的代码要简单很多,通过对K减一来判断不同状态
你需要找到一个策略来最大化一个数组的和,通过反转数组中的元素,最多进行 k 次反转。
这是一个贪心策略问题,我们可以采取以下策略来解决这个问题:
我们首先对数组进行排序。然后从左到右遍历数组,对于每个负数,如果 k>0,我们就把它变为正数,并且把 k 减 1。
如果我们遍历完所有的负数或者 k 已经变为 0,我们就停止遍历。此时有两种情况:
a. 如果 k 仍然大于 0,并且 k 是偶数,我们就不需要做任何改动,因为反转一个数两次等于没有反转。
b. 如果 k 是奇数,我们就需要将数组中最小的正数反转为负数,因为这样可以得到最大的和。
以下是这个问题的 Python 解决方案:
pythonnums.sort() i = 0 while i < len(nums) and nums[i] < 0 and k > 0: nums[i] = -nums[i] i += 1 k -= 1 return sum(nums) - (2 * min(nums) if k % 2 == 1 else 0)
这个算法的时间复杂度是 O(n log n),因为我们需要对数组进行排序。空间复杂度是 O(1),因为我们只需要常量的额外空间。
而参考代码按照绝对值大小降序排列,省去了很多条件判断
python
class Solution:
def largestSumAfterKNegations(self, A: List[int], K: int) -> int:
A.sort(key=lambda x: abs(x), reverse=True) # 第一步:按照绝对值降序排序数组A
for i in range(len(A)): # 第二步:执行K次取反操作
if A[i] < 0 and K > 0:
A[i] *= -1
K -= 1
if K % 2 == 1: # 第三步:如果K还有剩余次数,将绝对值最小的元素取反
A[-1] *= -1
result = sum(A) # 第四步:计算数组A的元素和
return result
134. 加油站 - 力扣(LeetCode)
这个问题是一个经典的算法问题,称为加油站问题。在这个问题中,我们需要找到一个加油站,从这个加油站出发,我们可以绕环路行驶一周。具体的算法思路如下:
- 创建两个变量,total_tank 和 curr_tank,用于跟踪我们的汽油总量和当前的汽油量。
- 创建一个变量,starting_station,用于记录我们开始的加油站。
- 对于每一个加油站 i,我们先计算出汽油的剩余量,也就是 gas[i] - cost[i]。然后更新 total_tank 和 curr_tank。如果 curr_tank 变为负数,说明我们不能从当前的出发点开始,因此我们需要更新 starting_station 为 i+1,并将 curr_tank 重置为 0。
- 在遍历完所有的加油站之后,如果 total_tank 仍然大于等于0,那么说明存在一个加油站可以满足条件,返回 starting_station;否则,返回 -1。
这个算法的时间复杂度是 O(n),因为我们只遍历了一次所有的加油站。
下面是使用 Python 实现的代码:
python
def canCompleteCircuit(gas, cost):
# 初始化总汽油量和当前汽油量为0
total_tank, curr_tank = 0, 0
# 初始化起始加油站为0
starting_station = 0
# 遍历每一个加油站
for i in range(len(gas)):
# 更新总汽油量和当前汽油量
total_tank += gas[i] - cost[i]
curr_tank += gas[i] - cost[i]
# 如果当前汽油量小于0,意味着从当前起始加油站出发无法到达第i个加油站
if curr_tank < 0:
# 更新起始加油站为下一个加油站,重置当前汽油量为0
starting_station = i + 1
curr_tank = 0
# 如果总汽油量大于等于0,返回起始加油站,否则返回-1,表示无法从任何加油站出发绕环路行驶一周
return starting_station if total_tank >= 0 else -1
在这段代码中,我们首先初始化 total_tank、curr_tank 和 starting_station。然后,我们遍历每一个加油站,并计算 total_tank 和 curr_tank。如果 curr_tank 小于 0,我们就更新 starting_station 并重置 curr_tank。最后,如果 total_tank 大于等于 0,我们就返回 starting_station,否则返回 -1。
下面是对"更新起始加油站为下一个加油站"的说明
这个算法中的这一步其实利用了一种"贪心"的策略。
假设从加油站 A 出发,你在加油站 B 首次发现汽油不够用了(即 A 到 B 的汽油不够了)。根据算法的策略,你将会考虑从加油站 B+1开始作为新的起点,并检查这一新的起点能否让你完成一整圈。
你可能会有疑问:既然 A 到 B 的汽油不够,为什么不考虑 A+1或者其他更早的加油站作为新的起点呢?
这是因为根据问题设定,所有的加油站总能提供的汽油量是大于或等于总的消耗量的,也就是说肯定存在一种可能的路径。现在,你从A 出发到 B 已经失败了,这表示从 A 到 B 这段路上,你收到的汽油总量是小于消耗量的。因此,如果你选择 A 和 B之间的任何一个加油站作为新的起点,从新的起点出发到达 B 时,你手上的汽油量更少,还是不够的。
所以,这个算法选择从 B+1 作为新的起点并不会漏掉任何可能的解。
135. 分发糖果 - 力扣(LeetCode)
这是一个典型的贪心算法问题。我们首先给每个孩子分发一颗糖果,然后我们按照以下两个规则来分发更多的糖果:
-
从左到右遍历孩子,如果一个孩子的评分比他左边的孩子高,那么这个孩子应该得到比他左边的孩子更多的糖果。
-
从右到左遍历孩子,如果一个孩子的评分比他右边的孩子高,且他目前的糖果数少于他右边的孩子,那么他应该得到和他右边孩子相同数量的糖果。
我们需要注意的是,最后一个孩子不需要和右边的孩子比较,因为他没有右边的孩子。
以下是Python代码实现:
python
def candy(ratings):
n = len(ratings)
candies = [1] * n
for i in range(1, n):
if ratings[i] > ratings[i-1]:
candies[i] = candies[i-1] + 1
for i in range(n-2, -1, -1):
if ratings[i] > ratings[i+1]:
candies[i] = max(candies[i], candies[i+1] + 1)
return sum(candies)
以上代码首先给每个孩子分发一颗糖果,然后从左到右和从右到左分别遍历孩子,根据上述两个规则给孩子分发更多的糖果,最后返回总的糖果数量。
这里很巧妙的一点是,通过两次遍历,先保证了每个孩子满足左边的情况,然后再解决右边的情况
总结
两次遍历有些意思,将复杂问题拆解为简单问题