代码随想录算法训练营第33天 || 1005.K次取反后最大化的数组和 || 134. 加油站 || 135. 分发糖果

文章介绍了三个编程问题的解决方案:1)通过排序和反转策略找到K次取反后数组的最大和;2)利用贪心算法解决加油站问题,确定能完成环路旅行的出发点;3)根据相邻比较规则,用动态规划策略解决分发糖果问题,确保评分高的孩子获得更多的糖果。
摘要由CSDN通过智能技术生成

代码随想录算法训练营第33天 || 1005.K次取反后最大化的数组和 || 134. 加油站 || 135. 分发糖果

1005.K次取反后最大化的数组和

题目介绍:

给你一个整数数组 nums 和一个整数 k ,按以下方法修改该数组:

  • 选择某个下标 i 并将 nums[i] 替换为 -nums[i]

重复这个过程恰好 k 次。可以多次选择同一个下标 i

以这种方式修改数组后,返回数组 可能的最大和

示例 1:

输入:nums = [4,2,3], k = 1
输出:5
解释:选择下标 1 ,nums 变为 [4,-2,3] 。

示例 2:

输入:nums = [3,-1,0,2], k = 3
输出:6
解释:选择下标 (1, 2, 2) ,nums 变为 [3,1,0,2] 。

示例 3:

输入:nums = [2,-3,-1,5,-4], k = 2
输出:13
解释:选择下标 (1, 4) ,nums 变为 [2,3,-1,5,4] 。

个人思路:

  • 先将数组排序,然后按起始下标对负数进行反转
    • 负数个数 > k,正常反转完k个,之后遍历剩下的元素即可
    • 负数个数 < k,反转完所有负数后,看看剩余次数为奇数还是偶数
      • 若为偶数,就不需要再反转了
      • 若为奇数,只需要再反转一个正数就行,比较一下此刻元素和上一个元素的绝对值,反转较小的那个即可
class Solution {
    public int largestSumAfterKNegations(int[] nums, int k) {
        int result = 0;
        Arrays.sort(nums);
        int i = 0;
        for (; i < k && i < nums.length; i++) {
            if (nums[i] >= 0)
                break;
            nums[i] = Math.abs(nums[i]);
            result += nums[i];
        }
        if ((k - i) % 2 == 1) {//还剩奇数个反转
            //1.数组已遍历完毕(必然是负数,即其绝对值最小)--->将最后一个反转
            //2.1数组未遍历完毕,比较正负分界处两值绝对值,负数的绝对值较大
            //2.2数组第一个数就是正数,分界处,正数绝对值大
            if (i >= nums.length || i > 0 && nums[i] > nums[i - 1])
                result -= 2 * nums[i - 1];
             else nums[i] *= (-1);
        }
        //遍历剩余元素
        for (; i < nums.length; i++) {
            result += nums[i];
        }
        return result;
    }
}

题解解析:

将数组按绝对值大小进行排序,从大的那一侧遍历,遇到负数就反转

若到末尾还有反转次数,则看次数是奇数还是偶数进行不同操作

奇数则反转最后一个数,偶数则不反转

134. 加油站

题目介绍:

在一条环路上有 n 个加油站,其中第 i 个加油站有汽油 gas[i] 升。

你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。

给定两个整数数组 gascost ,如果你可以绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1 。如果存在解,则 保证 它是 唯一 的。

示例 1:

输入: 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:

输入: 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 升汽油。
因此,无论怎样,你都不可能绕环路行驶一周。

个人思路:

首先,我们需要使用几个参数去记录一些关键信息

int result = 0;//记录出发的下标
int excess_sum = 0;//遍历全程后,剩下的油
int[] excess_gas = new int[gas.length];//记录每一处加油与油耗的差值
int[] sum_gas = new int[gas.length];//当前下标记录从某个点起至当前点所剩油

然后,我们通过一个for循环遍历整个过程,过程中不断刷新sum_gas,当其为负数时,重新确定出发点,继续循环过程

如果真的能有某个点能循环走完全程,那必然在遍历过程中就碰到;反过来,遍历结束的result不一定就是结果,我们还得看excess_sum是否为非负

class Solution {
    public int canCompleteCircuit(int[] gas, int[] cost) {
        int result = 0;//记录出发的下标
        int excess_sum = 0;//遍历全程后,剩下的油
        int[] excess_gas = new int[gas.length];//记录每一处加油与油耗的差值
        int[] sum_gas = new int[gas.length];//当前下标记录从某个点起至当前点所剩油
        for (int i = 0; i < gas.length; i++) {
            excess_gas[i] = gas[i] - cost[i];
            sum_gas[i] = i == 0 ? excess_gas[i] : excess_gas[i] + sum_gas[i - 1];
            excess_sum += excess_gas[i];
             if (sum_gas[i] < 0) {
                sum_gas[i] = 0;
                result = i + 1;
            }
        }
        return excess_sum >= 0 ? result : -1;
    }
}

题解解析:

法一:一种很巧妙的方法,但并非贪心,了解即可

  • 情况一:如果gas的总和小于cost总和,那么无论从哪里出发,一定是跑不了一圈的
  • 情况二:rest[i] = gas[i]-cost[i]为一天剩下的油,i从0开始计算累加到最后一站,如果累加没有出现负数,说明从0出发,油就没有断过,那么0就是起点。
  • 情况三:如果累加的最小值是负数,汽车就要从非0节点出发,从后向前,看哪个节点能这个负数填平,能把这个负数填平的节点就是出发节点。反过来思考,正序遍历那个点能填平最小的负数,那后面必然可以走完全程,因为整体是够油的
class Solution {
public:
    int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
        int curSum = 0;
        int min = INT_MAX; // 从起点出发,油箱里的油量最小值
        for (int i = 0; i < gas.size(); i++) {
            int rest = gas[i] - cost[i];
            curSum += rest;
            if (curSum < min) {
                min = curSum;
            }
        }
        if (curSum < 0) return -1;  // 情况1
        if (min >= 0) return 0;     // 情况2
                                    // 情况3
        for (int i = gas.size() - 1; i >= 0; i--) {
            int rest = gas[i] - cost[i];
            min += rest;
            if (min >= 0) {
                return i;
            }
        }
        return -1;
    }
};

法二:和我的想法类似,但是代码优化了不少

class Solution {
    public int canCompleteCircuit(int[] gas, int[] cost) {
        int result = 0;//记录出发的下标
        int excess_sum = 0;//遍历全程后,剩下的油
        int curSum = 0;//当前下标记录从某个点起至当前点所剩油
        for (int i = 0; i < gas.length; i++) {
            curSum += gas[i] - cost[i];
            excess_sum += gas[i] - cost[i];
            if (curSum < 0) {
                curSum = 0;
                result = i + 1;
            }
        }
        return excess_sum >= 0 ? result : -1;
    }
}

135. 分发糖果

题目介绍:

n 个孩子站成一排。给你一个整数数组 ratings 表示每个孩子的评分。

你需要按照以下要求,给这些孩子分发糖果:

  • 每个孩子至少分配到 1 个糖果。
  • 相邻两个孩子评分更高的孩子会获得更多的糖果。

请你给每个孩子分发糖果,计算并返回需要准备的 最少糖果数目

示例 1:

输入:ratings = [1,0,2]
输出:5
解释:你可以分别给第一个、第二个、第三个孩子分发 2、1、2 颗糖果。

示例 2:

输入:ratings = [1,2,2]
输出:4
解释:你可以分别给第一个、第二个、第三个孩子分发 1、2、1 颗糖果。
     第三个孩子只得到 1 颗糖果,这满足题面中的两个条件。

个人思路:

我的想法是标记从起点开始,遍历标记每个点的相对上下位置,下标0处为0,大的就较前一个数+1,小的就-1,

但是相等的处理上较为复杂,想到反单调变化(),但遇到连续很多个孩子一样成绩也要再考虑,时间原因直接看题解了

题解解析:

注意:题目说的是,比相邻的大,就要比他得到的糖果更多,如果相同就没有这个限制。相邻这个条件我们可以分成两边来考虑

我们可以先比较一边再去比较另一边,如果一次遍历同时比较两边一定会失衡。

局部最优:只要右边孩子比左边孩子大,右边孩子就多一个糖果,全局最优:相邻的孩子中,评分高的右孩子获得比左孩子更多的糖果。

步骤:

  • 我们先从首位起正序遍历,向右比较,得出仅一侧的全局最优,但此时不满足向左比较的条件
  • 再从尾部起逆序遍历,向左比较,也能逐个得到结果,过程中我们要保留与正序遍历的结果相比中较大的结果(其能满足相邻比较的条件)

向左比较不能从首部遍历,不然会重复第一步的过程,没有意义

class Solution {
    public int candy(int[] ratings) {
        int[] candy = new int[ratings.length];

        for (int i = 0; i < candy.length; i++) {
            if (i > 0 && ratings[i] > ratings[i - 1])
                candy[i] = candy[i - 1] + 1;
            else candy[i] = 1;
        }

        for (int i = candy.length - 1; i > 0; i--) {
            if (ratings[i] < ratings[i - 1])
                candy[i - 1] = Integer.max(candy[i] + 1, candy[i - 1]);
        }
        int nums = 0;
        for (int i = 0; i < candy.length; i++) {
            nums += candy[i];
        }
        return nums;
    }
}
第二十二算法训练营主要涵盖了Leetcode题目中的三道题目,分别是Leetcode 28 "Find the Index of the First Occurrence in a String",Leetcode 977 "有序数组的平方",和Leetcode 209 "长度最小的子数组"。 首先是Leetcode 28题,题目要求在给定的字符串中找到第一个出现的字符的索引。思路是使用双指针来遍历字符串,一个指向字符串的开头,另一个指向字符串的结尾。通过比较两个指针所指向的字符是否相等来判断是否找到了第一个出现的字符。具体实现的代码如下: ```python def findIndex(self, s: str) -> int: left = 0 right = len(s) - 1 while left <= right: if s[left == s[right]: return left left += 1 right -= 1 return -1 ``` 接下来是Leetcode 977题,题目要求对给定的有序数组中的元素进行平方,并按照非递减的顺序返回结果。这里由于数组已经是有序的,所以可以使用双指针的方法来解决问题。一个指针指向数组的开头,另一个指针指向数组的末尾。通过比较两个指针所指向的元素的绝对值的大小来确定哪个元素的平方应该放在结果数组的末尾。具体实现的代码如下: ```python def sortedSquares(self, nums: List[int]) -> List[int]: left = 0 right = len(nums) - 1 ans = [] while left <= right: if abs(nums[left]) >= abs(nums[right]): ans.append(nums[left ** 2) left += 1 else: ans.append(nums[right ** 2) right -= 1 return ans[::-1] ``` 最后是Leetcode 209题,题目要求在给定的数组中找到长度最小的子数组,使得子数组的和大于等于给定的目标值。这里可以使用滑动窗口的方法来解决问题。使用两个指针来表示滑动窗口的左边界和右边界,通过移动指针来调整滑动窗口的大小,使得滑动窗口中的元素的和满足题目要求。具体实现的代码如下: ```python def minSubArrayLen(self, target: int, nums: List[int]) -> int: left = 0 right = 0 ans = float('inf') total = 0 while right < len(nums): total += nums[right] while total >= target: ans = min(ans, right - left + 1) total -= nums[left] left += 1 right += 1 return ans if ans != float('inf') else 0 ``` 以上就是第二十二算法训练营的内容。通过这些题目的练习,可以提升对双指针和滑动窗口等算法的理解和应用能力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值