秋招算法备战第34天 | 1005.K次取反后最大化的数组和、134. 加油站、135. 分发糖果

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 次反转。

这是一个贪心策略问题,我们可以采取以下策略来解决这个问题:

  1. 我们首先对数组进行排序。然后从左到右遍历数组,对于每个负数,如果 k>0,我们就把它变为正数,并且把 k 减 1。

  2. 如果我们遍历完所有的负数或者 k 已经变为 0,我们就停止遍历。此时有两种情况:

    a. 如果 k 仍然大于 0,并且 k 是偶数,我们就不需要做任何改动,因为反转一个数两次等于没有反转。

    b. 如果 k 是奇数,我们就需要将数组中最小的正数反转为负数,因为这样可以得到最大的和。

以下是这个问题的 Python 解决方案:

python 复制代码
    nums.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)

这个问题是一个经典的算法问题,称为加油站问题。在这个问题中,我们需要找到一个加油站,从这个加油站出发,我们可以绕环路行驶一周。具体的算法思路如下:

  1. 创建两个变量,total_tank 和 curr_tank,用于跟踪我们的汽油总量和当前的汽油量。
  2. 创建一个变量,starting_station,用于记录我们开始的加油站。
  3. 对于每一个加油站 i,我们先计算出汽油的剩余量,也就是 gasi - costi。然后更新 total_tank 和 curr_tank。如果 curr_tank 变为负数,说明我们不能从当前的出发点开始,因此我们需要更新 starting_station 为 i+1,并将 curr_tank 重置为 0。
  4. 在遍历完所有的加油站之后,如果 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)

这是一个典型的贪心算法问题。我们首先给每个孩子分发一颗糖果,然后我们按照以下两个规则来分发更多的糖果:

  1. 从左到右遍历孩子,如果一个孩子的评分比他左边的孩子高,那么这个孩子应该得到比他左边的孩子更多的糖果。

  2. 从右到左遍历孩子,如果一个孩子的评分比他右边的孩子高,且他目前的糖果数少于他右边的孩子,那么他应该得到和他右边孩子相同数量的糖果。

我们需要注意的是,最后一个孩子不需要和右边的孩子比较,因为他没有右边的孩子。

以下是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)

以上代码首先给每个孩子分发一颗糖果,然后从左到右和从右到左分别遍历孩子,根据上述两个规则给孩子分发更多的糖果,最后返回总的糖果数量。

这里很巧妙的一点是,通过两次遍历,先保证了每个孩子满足左边的情况,然后再解决右边的情况

总结

两次遍历有些意思,将复杂问题拆解为简单问题

相关推荐
江畔柳前堤8 小时前
大语言模型分布式训练:从并行策略到万卡工程的系统梳理
人工智能·分布式·深度学习·算法·目标检测·机器学习·语言模型
Forever Nore9 小时前
LeetCode 4 寻找两个正序数组的中位数 - 二分
算法·leetcode
罗西的思考11 小时前
【OpenClaw具身硬件】MiniClaw 阅读笔记---(1)基础
人工智能·算法·机器学习
蛋先生DX11 小时前
大模型参数存储格式揭秘:BF不是男朋友
深度学习·算法·llm
爱跳舞的烤冷面12 小时前
自学嵌入式第22天(数据结构——哈希)
数据结构·算法·哈希算法
猎嘤一号12 小时前
博弈论(Game Theory)的理论、算法与工程
人工智能·算法·安全·博弈论
月光船幽幽12 小时前
影子模式下保护 logits 不被修改
人工智能·python·算法
马拉AI12 小时前
腾讯开源 Agent 记忆系统,AI“换对话就忘”的问题有了新解法(附安装使用教程)
人工智能·算法·开源·科研
地平线开发者14 小时前
征程6工具链模型X86推理方式说明
算法
地平线开发者14 小时前
【征程6】校准量化中HistogramObserver解析
算法