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

视频讲解:

贪心算法,这不就是常识?还能叫贪心?LeetCode:1005.K次取反后最大化的数组和_哔哩哔哩_bilibili

贪心算法,得这么加油才能跑完全程!LeetCode :134.加油站_哔哩哔哩_bilibili

贪心算法,两者兼顾很容易顾此失彼!LeetCode:135.分发糖果_哔哩哔哩_bilibili

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

思路:将所有负数全部翻正;将大的负数翻正;将最小正数反复翻正来消耗k。这三种思路下即可求解。

// 时间复杂度O(nlogn),排序的开销
// 空间复杂度O(1)
class Solution {
    public int largestSumAfterKNegations(int[] nums, int k) {
        Arrays.sort(nums);
        
        // 情况1,负数的个数大于k,那么直接求和就可以
        // 情况2,负数的个数少于k,那么nums中将没有负数,index会定位到最小的正数,用来消耗剩余的k,
        // 情况3,全部都是正数,也是找到最小的正数消耗k

        // 将所有的负数变成正数或者把k消耗完
        int sum = 0;
        int index = 0;
        for(int i=0; i0 && nums[i]<0){
                nums[i] = Math.abs(nums[i]);
                k--;
            }
            index = nums[i]

 

134. 加油站

思路:本题是看了视频讲解后解决,用的是代码随想录网站上的第二种解法。在视频给出的求和每个城市的剩余油量的思路之后,写了一个大的while循环,但是超时。

        while(count < n){
            int left = gas[j] - cost[j];
            sum += left;
            // 如果出现剩余油量的累加和是负数,表明从起点开始到当前j位置的城市行走油是不够的
            // 所以起点应该不在start到j这个范围内找,可以往start前去找,也可以往i之后去找
            if(sum < 0){
                start = (j+1)%n;
                j = start;
                count = 0;
                sum = 0;
            }
            else{
                count++;
                j = (j+1)%n;
            }
        }

所以采用了网站上第二种解法完成了解题。

// 时间复杂度O(n)
// 空间复杂度O(1)

class Solution {
    public int canCompleteCircuit(int[] gas, int[] cost) {

        int curSum = 0;
        int totalSum = 0;   //声明这个变量的目的就是为了判断当前的gas和cost能够实现一次环路加油
        int start = 0;

        for(int i=0; i= 0)
            return start;
        else
            return -1;
    }
}

这种解法的贪心策略在于每一次我都希望车辆经过城市后剩余油量可以增加,出现减少也可以接受,但是不可以出现为负的,代表从start开始到当前j的位置是不能完成环路访问的,需要去访问j之后和start之前的部分(因为本题是一个回路,所以start之前最终会走到数组末尾,也就是j之后)。此外关于第二种解法,其特点是非常的精炼,但是其中有个关键的前提就是题目明确要有解的仅仅有一个,所以才可以使用一个for循环从0位置开始遍历就得到最终的情况,否则就是要使用超时的while循环,去一个一个节点尝试。

另外第二种解法中精炼之处还在于如果有解,且start到j位置剩余油量和小于0,那么起点一定在j之后,因为从0开始遍历的,不可能再往前了,所以解一定在新的范围内,不会在旧的范围之内。另外一定是两个累积和变量,一个用来且仅仅用来判断全局是否有解,有解的话直接返回start,因为其一定可以指向累积和是大于零的起始位置k(即可行起点),其他的范围已经全是负数了,何况还是有解,所以直接返回start就行。

135. 分发糖果

思路:本题中明确 i 位置需要参考 i-1 和 i+1位置的评分,评分高者所具备的糖果数量一定大于低的,但是题目中明确需要最少的糖果数,所以要是需要比前一位置来的糖果数多,那么比其大一就是既满足评分比较的要求,也是满足糖果总数最小的要求。

其次,本题的贪心策略就是给每一个位置的学生我都可以在满足评估比较结果的前提下分发最少的糖果数。因此我们开始遍历,遍历的思路就是如果题目中所讲,比较评分,如果出现评分差异并且糖果数还不能体现出差异,那么就需要增加评分优胜者的糖果数量;反之已经满足的话就不用进行增加了。因此我们假设当前是i位置的判断,i会对i-1和i+1位置的糖果数产生影响,i+1会对i+2和i位置产生影响,但是i+1在判断时的影响会传播至i-1处,但是我们此时修改不到,这就是本题的最大陷阱,举个例子8,10,7,2,1,我们从左向右遍历,统计了7的时,会从2内获取优胜感;而统计2时,从1处获得的优胜感会传播到7,因此2还可以触碰到7;但是在2从1处获得优胜感后传播到7,然后也要传播到10,但2只会与相邻的元素进行更新,所以出错了。

所以我们采用这样的方式进行实现。

// 时间复杂度O(2n)
// 空间复杂度O(1)

class Solution {
    public int candy(int[] ratings) {

        if(ratings.length == 1)
            return 1;
        
        int[] candies = new int[ratings.length];
        int sum = ratings.length;   //初始化总消耗糖果数,即默认是1

        for(int i=0; i=0)
                if(ratings[i]>ratings[i-1])
                    if(candies[i]<=candies[i-1])
                        candies[i] = candies[i-1]+1;
        }
        for(int i=ratings.length-1; i>=0; i--){
            if(i+1ratings[i+1])
                    if(candies[i]<=candies[i+1])
                        candies[i] = candies[i+1]+1;
            sum += candies[i];
        }   
        return sum;
    }
}

两个方向的遍历的理由可以这么解释,第一回看上面的例子,发现我们的思路其实没有问题,但是统计不同节点时所产生的新传播需要在全局数组进行一遍更新,这种更新使用一次for循环就可以完成。而我们遍历方向是从左向右,传播之后从右向左,所以这个初始计算加传播更新的过程与两个方向的循环就对上了。另一种理由就是我需要在每个位置比较相邻的位置,那么其实就是需要参照两个方向的元素,所以我们使用一次循环专心处理一个方向,另一个方向可以在上一个方向的基础上满足剩下方向中存在的大小关系,并且还可以对上一个方向存在的大小关系进行补充。

你可能感兴趣的:(算法,数据结构)