DAY38:贪心算法(五)K次取反后最大数组和+加油站

这篇具有很好参考价值的文章主要介绍了DAY38:贪心算法(五)K次取反后最大数组和+加油站。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

  • 本题重点是逻辑问题,同时复习static和sort的自定义操作与时间复杂度

给你一个整数数组 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]

提示:

  • 1 <= nums.length <= 10^4
  • -100 <= nums[i] <= 100
  • 1 <= k <= 10^4

思路

本题主要含义是,给出一个数组,基于这个数组在任意可重复的下标k次取反,计算k次取反后的最大和。

首先,数组内有正数和负数,我们优先对负数进行取反。在负数里,我们又要优先对绝对值最大的负数进行取反

当负数全部取反,数组里全部是正数之后,此时次数还没有用完,我们需要再优先对绝对值最小的正数取反

局部最优就是,优先找绝对值最大的负数/绝对值最小的正数取反。全局最优就是最后的数组和最大

本题实际上涉及到了两次贪心。一次是数组有正有负,一次是数组里全是正数。

直接升序排序的写法

  • 第一步:全部转成正数
  • 第二步:全转成正数之后重新排序,因为转成正数之后还有可能出现比现有正数更小的正数
最开始的写法:逻辑错误
class Solution {
public:
    int largestSumAfterKNegations(vector<int>& nums, int k) {
        //先对数组进行排序,这里可以考虑自定义比较函数,按照绝对值的大小进行排序,也可以直接升序排序
        sort(nums.begin(),nums.end());
        int sum=0;
        for(int i=0;i<nums.size();i++){
            //cout<<nums[i]<<endl;
            if(nums[i]<0&&k>0){
                nums[i]=-nums[i];
                //cout<<nums[i]<<endl;
                k--;
                continue;
            }
            if(nums[i]>=0&&k>0){
                while(k>0){
                    nums[i]=-nums[i];
                    cout<<nums[i]<<endl;
                    k--;
                }   
            }
            //if(k==0) break;
        }
        for(int i=0;i<nums.size();i++){
            //cout<<nums[i]<<endl;
            sum += nums[i];
        }
        return sum;

    }
};

DAY38:贪心算法(五)K次取反后最大数组和+加油站,刷题记录,贪心算法,算法,c++,leetcode
这种写法出现了逻辑错误,原因是出现了如下案例:

DAY38:贪心算法(五)K次取反后最大数组和+加油站,刷题记录,贪心算法,算法,c++,leetcode

本题中需要在最小的正数上面消耗次数,而不是把所有的负数转正数之后,在原排序基础上消耗原有的正数!

修改版
  • 这种写法需要排序两次,因为负数全转成正数之后,很有可能出现更小的正数
class Solution {
public:
    int largestSumAfterKNegations(vector<int>& nums, int k) {
        //先对数组进行排序,这里可以考虑自定义比较函数,按照绝对值的大小进行排序,也可以直接升序排序
        sort(nums.begin(),nums.end());
        int sum=0;
        //先把所有负数转成正数
        for(int i=0;i<nums.size();i++){
            //cout<<nums[i]<<endl;
            if(nums[i]<0&&k>0){
                nums[i]=-nums[i];
                //cout<<nums[i]<<endl;
                k--;
            }
        }
        //再重新排序,并在重排后的第一个元素上消耗剩余所有次数
        sort(nums.begin(),nums.end());
        while(k>0){
            nums[0]=-nums[0];
            k--;
        }
        //求和
        for(int i=0;i<nums.size();i++){
            cout<<nums[i]<<endl;
            sum += nums[i];
        }
        return sum;

    }
};
时间复杂度

这种写法需要排序两次,时间复杂度主要取决于sort的复杂度

在C++中,sort函数的时间复杂度是O(nlogn),其中n是数组的长度。因此,上述代码的时间复杂度是O(nlogn)(对原数组排序)+ O(n)(遍历数组对负数进行操作)+ O(nlogn)(再次对数组排序)+ O(n)(对剩余的k进行处理和求和)= O(nlogn)

实际上,时间复杂度为O(nlogn + nk),先进行了一次全体元素的排序(O(nlogn)),然后可能对每个元素进行多次操作(O(nk))

排序次数过多导致开销会增大。此时我们考虑优化。

自定义sort对绝对值排序的写法

  • 这种写法,我们在一开始,就对元素绝对值进行排序。
  • 注意绝对值排序一定要降序排序!因为我们要先消耗数值大的负数!sort本身是升序,只用sort的话数值大的负数会在前面,但是这里自定义了绝对值,就必须把数值大的放在前面,用**greater(降序)**来实现!
class Solution {
public:
    int sum=0;
    static bool cmp(int a,int b){
        if(abs(a)>abs(b)) 
            return true;//绝对值降序排序,因为要先处理大的负数
        else 
           return false;
    }
    int largestSumAfterKNegations(vector<int>& nums, int k) {
       sort(nums.begin(),nums.end(),cmp);//绝对值降序排序
       for(int i=0;i<nums.size();i++){
           //第一次遍历的时候,只在负数上消耗次数!
           if(nums[i]<0&&k>0){
               nums[i]=-nums[i];
               k--;
           }
          if(k==0) break;
       }
       //如果此时还有k没消耗完,看看k是奇数or偶数,如果是偶数就不用管了
       if(k%2==1){
           //如果是奇数,直接最小值取反就是结果
           nums[nums.size()-1]= - nums[nums.size()-1];
       }
       for(int i:nums){//遍历Nums
           sum+=i;  //这种写法,i就是元素本身,不能写成nums[i]否则会越界
       }
        return sum;
    }
};
sort的自定义比较函数cmp必须声明为static的原因

c++静态变量成员函数和全局函数的区别_成员函数全局函数_大磕学家ZYX的博客-CSDN博客

cmp函数被声明为static,这是因为std::sort需要一个能够在全局访问的函数,或者一个lambda函数

如果cmp不是static的,那么这就意味着它需要一个对象上下文(因为非静态成员函数都有一个隐含的this指针参数)。然而在这种情况下,我们不能给出这样一个上下文。所以,我们需要使cmp函数成为一个静态成员函数,这样它就可以在没有对象上下文的情况下被调用

static的用法补充:static在 C++ 中的四种用法_大磕学家ZYX的博客-CSDN博客

std::sort升降序的问题(默认升序)

注意,在C++中,std::sort函数默认的排序方式是从小到大,也就是升序排序。这个函数会对输入范围内的元素进行排序,使得排序后的元素序列是非递减的。

如果要实现降序排序,你可以给std::sort函数提供第三个参数,即一个自定义的比较函数或者lambda表达式。这个比较函数定义了两个元素的比较规则。

例如,如果想对candidates数组进行降序排序,你可以这样做:

std::sort(candidates.begin(), candidates.end(), std::greater<int>());

在这个代码中,std::greater<int>()是一个函数对象,它定义了一个规则,使得sort函数会按照这个规则进行排序,也就是降序排序

另一种方式是使用lambda表达式来定义比较规则:

std::sort(candidates.begin(), candidates.end(), [](int a, int b) {return a > b;});

在这个代码中,[](int a, int b) {return a > b;}是一个lambda表达式,它接受两个参数ab,如果a > b,则返回true,这样sort函数就会按照这个规则进行降序排序

时间复杂度

优化写法时间复杂度同样主要取决于排序的复杂度,即O(nlogn)。遍历数组进行操作的时间复杂度是O(n)。所以总的时间复杂度是O(nlogn)

总结

第二种写法的优化主要体现在两个地方:

  1. 通过使用绝对值的大小进行排序,可以优先处理绝对值大的元素。这样,不仅可以减少不必要的符号反转操作,而且可以更好地利用操作次数,因为对绝对值大的元素进行反转,可以获得更大的结果。
  2. 通过在第一次遍历中只在负数上消耗次数,并在处理完所有负数后,不需要再次进行排序,因为此时已经是从小到大的正数顺序(本来就是绝对值排序)。再根据剩余次数的奇偶性进行相应操作,可以避免对每个元素进行多次操作,进一步降低时间复杂度。
  3. sort升序的写法和绝对值降序的写法如下图所示。局部最优:让绝对值大的负数变为正数,当前数值达到最大,整体最优:整个数组和达到最大

DAY38:贪心算法(五)K次取反后最大数组和+加油站,刷题记录,贪心算法,算法,c++,leetcode

134.加油站

  • 本题可以和 53.最大子数组和 放在一起来看,都是属于遇到了不需要的结果,直接统计量清零,重新开始算起点的类型。

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

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

给定两个整数数组 gas 和 cost ,如果你可以按顺序绕环路行驶一周,则返回出发时加油站的编号,否则返回 -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 升汽油。
因此,无论怎样,你都不可能绕环路行驶一周。

提示:

  • gas.length == n
  • cost.length == n
  • 1 <= n <= 10^5
  • 0 <= gas[i], cost[i] <= 10^4

思路

本题是模拟加油站跑圈的过程,每到一个站点补充油,开到下一个站点消耗油。需要让目前的油+补充油高于消耗油,才能开到下一个站点。大致情况如图:

DAY38:贪心算法(五)K次取反后最大数组和+加油站,刷题记录,贪心算法,算法,c++,leetcode
本题的暴力解法是两个for循环,一个循环模拟每个站点作为起始位置的情况,一个循环看这个位置能不能跑一圈,时间复杂度就是n^2。暴力解思路简单,但是跑一圈的过程不太好做。while模拟转圈过程比较复杂

本题最好还是贪心的思路来解。本题有补充+消耗,我们要看的就是对油箱增加作用还是消耗作用。也就是统计净增长量。如下图所示。

DAY38:贪心算法(五)K次取反后最大数组和+加油站,刷题记录,贪心算法,算法,c++,leetcode
我们用curSum变量去累积每个站点油箱的状态。

一旦油箱在这个站点状态成为负数,说明从这个位置之前的任意位置开始,都不可能到达终点!此时我们直接选择负数状态位置的i+1作为新的起始点,之前的起始点就全部抛弃掉

遇到i位置的curSum<0,就直接让i+1作为起点,这种逻辑是否正确可以画图来看:

DAY38:贪心算法(五)K次取反后最大数组和+加油站,刷题记录,贪心算法,算法,c++,leetcode
贪心的思路就是不断累加curSum,局部最优:当前累加rest[i]的和curSum一旦小于0,起始位置至少要是i+1,因为从i之前开始一定不行。全局最优:找到可以跑一圈的起始位置

局部最优可以推出全局最优并且想不出来反例,反例在上图有大概的证明,因此可以尝试贪心。

完整版

  • 53.最大子数组和 一样,本题同样也不需要改变for的遍历方式!startIndex只是用来记录位置方便返回,实际上重新开始遍历的时候,只需要把统计量curSum置零就可以了!
  • 净增长量如果<0,说明此时油箱状态成了负数,也就是说在此位置之前的点,都不可能跑完一圈;此时应重新置零,开始统计新的净增长,直到净增长量不是0,才说明能够跑完一圈
class Solution {
public:
    int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
        int curSum=0;
        int totalSum=0;//统计所有的rest,看是不是全是负数,全是负数说明不可能跑完一圈
        for(int i=0;i<gas.size();i++){
            totalSum+=(gas[i]-cost[i]);
        }
        //如果净增长到最后都是负数,返回-1
        if(totalSum<0)  return -1;
        
        int startIndex=0;//记录i+1作为起始位置,找到合适的起始i
        for(int i=0;i<gas.size();i++){
            curSum += (gas[i]-cost[i]);//统计剩余油量
            if(curSum<0){//i之前的油量rest累加为负数
                startIndex=i+1;
                curSum=0;//重新置零,开始统计新的净增长,直到最后净增长量不是0,才说明能够跑完一圈
            }
        }
		return startIndex;
    }
};
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

总结

贪心的主要策略就是局部最优可以推出全局最优,进而求得起始位置。本题和 53.最大子数组和 都属于遇到了不需要结果,直接重置统计量,相当于重置起点的类型。文章来源地址https://www.toymoban.com/news/detail-536247.html

到了这里,关于DAY38:贪心算法(五)K次取反后最大数组和+加油站的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【贪心算法Part03】| 1005.K次取反后最大化的数组和、134.加油站、135.分发糖果

    目录 🎈LeetCode1005.K次取反后最大化的数组和  🎈LeetCode134.加油站 🎈LeetCode135.分发糖果 链接:1005.K次取反后最大化的数组和 给你一个整数数组  nums  和一个整数  k  ,按以下方法修改该数组: 选择某个下标  i  并将  nums[i]  替换为  -nums[i]  。 重复这个过程恰好  k  次

    2024年02月16日
    浏览(31)
  • LeetCode刷题笔记【25】:贪心算法专题-3(K次取反后最大化的数组和、加油站、分发糖果)

    参考前文 参考文章: LeetCode刷题笔记【23】:贪心算法专题-1(分发饼干、摆动序列、最大子序和) LeetCode刷题笔记【24】:贪心算法专题-2(买卖股票的最佳时机II、跳跃游戏、跳跃游戏II) LeetCode链接:https://leetcode.cn/problems/maximize-sum-of-array-after-k-negations/description/ 首先 sor

    2024年02月09日
    浏览(26)
  • 【Leetcode60天带刷】day33回溯算法——1005.K次取反后最大化的数组和 134. 加油站 135. 分发糖果

    ​ 1005. K 次取反后最大化的数组和 给你一个整数数组  nums  和一个整数  k  ,按以下方法修改该数组: 选择某个下标  i  并将  nums[i]  替换为  -nums[i]  。 重复这个过程恰好  k  次。可以多次选择同一个下标  i  。 以这种方式修改数组后,返回数组  可能的最大和  。

    2024年02月11日
    浏览(25)
  • K 次取反后最大化的数组和【贪心算法】

    1005 . K 次取反后最大化的数组和 给你一个整数数组 nums 和一个整数 k ,按以下方法修改该数组: 选择某个下标 i 并将 nums[i] 替换为 -nums[i] 。 重复这个过程恰好 k 次。可以多次选择同一个下标 i 。 以这种方式修改数组后,返回数组 可能的最大和 。 关于 nums = IntStream.of(nums

    2024年02月11日
    浏览(32)
  • 贪心算法|1005.K次取反后最大化的数组和

    力扣题目链接 有没有不理解的语法知识呢? sort函数中的比较函数cmp(),即 void sort( iterator start, iterator end, StrictWeakOrdering cmp ); sort函数头文件为: #include algorithm 其中,cmp函数可以自己编写,自己决定逻辑,包括cmp的命名也是自己决定的。 示例如下:  代码随想录 (programmerc

    2024年04月09日
    浏览(50)
  • LeetCode-1005-K次取反后最大化的数组和-贪心算法

    题目描述: 给你一个整数数组 nums 和一个整数 k ,按以下方法修改该数组: 选择某个下标 i 并将 nums[i] 替换为 -nums[i] 。 重复这个过程恰好 k 次。可以多次选择同一个下标 i 。 以这种方式修改数组后,返回数组 可能的最大和 。 LeetCode-1005题目链接 思路见注释~ 代码实现

    2024年02月10日
    浏览(24)
  • C++力扣题目1005--K次取反后最大化的数组和 134--加油站 135--分发糖果

    力扣题目链接(opens new window) 给定一个整数数组 A,我们只能用以下方法修改该数组:我们选择某个索引 i 并将 A[i] 替换为 -A[i],然后总共重复这个过程 K 次。(我们可以多次选择同一个索引 i。) 以这种方式修改数组后,返回数组可能的最大和。 示例 1: 输入:A = [4,2,3],

    2024年01月21日
    浏览(32)
  • 力扣 1005. K 次取反后最大化的数组和

    题目来源:https://leetcode.cn/problems/maximize-sum-of-array-after-k-negations/description/ C++题解1:最直接的想法就是负的变正的,如果负的元素数量小于k,就挑选绝对值大的负数变正;如果负的元素数量大于k,那么还需要根据剩下的k(待变换数)的奇偶性来判断,偶数就不用管了,奇数

    2024年02月16日
    浏览(25)
  • DAY36:贪心算法(三)最大子数组和+买卖股票最佳时机

    给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。 子数组 是数组中的一个连续部分。 示例 1: 示例 2: 示例 3: 提示: 1 = nums.length = 10^5 -10^4 = nums[i] = 10^4 枚举思路 本题的暴力解法就是两个for循环,一个for循环遍

    2024年02月12日
    浏览(39)
  • 加油站【贪心算法】

    加油站 在一条环路上有 n 个加油站,其中第 i 个加油站有汽油 gas[i] 升。 你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。 给定两个整数数组 gas 和 cost ,如果你可以按顺序绕环路行

    2024年02月11日
    浏览(22)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包