代码随想录训练营第34天|1005. K 次取反后最大化的数组和, 134. 加油站,135.分发糖果 (需要二刷)

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

如果没有贪心的思考方式(局部最优,全局最优),很容易陷入贪心简单题凭感觉做,贪心难题直接不会做,其实这样就锻炼不了贪心的思考方式了

根据题目给出数组元素的范围为【-100,100】,所以可以新建一个数组number把nums的元素映射到number数组中,

然后遍历number数组找到最小数字,

如果把所有负数都取反后k还是>0,那么就要将最小的正数取反,这个地方要注意正数在number的映射是大于100的,要取反应该重新调整i的位置,即i=200-i;方便后面的求和运算

 代码实现

class Solution {
    public int largestSumAfterKNegations(int[] nums, int k) {
        //每次选择负数中最小的进行取反

        int[] number=new int[201];//-100 <= A[i] <= 100,这个范围的大小是201
        for(int num:nums){
            number[100+num]++;//将[-100,100]映射到[0,200]上

        }
        int i=0;
        while(k>0){
            while(number[i]==0){i++;}//找到nums中最小数字
            number[i]--;//此数字个数-1
            number[200-i]++;//其相反数个数+1
            if(i>100){//若最小数的索引>100,则新的最小数索引应为200-i,(索引即number[]数组的下标),最小数是正数,那么取反后就是负数了
                i=200-i;//纠正i的位置是为了下面求和遍历能够正确,这是一个坑
            }
            k--;
        }

        //求和
        int sum=0;
        for(int j=i;j<number.length;j++){
            sum+=(j-100)*number[j];  //j-100是数字大小,number[j]是该数字出现次数
        }
        return sum;


    }
}

这里有1个坑 :这种情况,因为最小值是正数的,在while循环后i的大小就停止了

所以要加上这段代码纠正i的位置

 if(i>100){//若最小数的索引>100,则新的最小数索引应为200-i,(索引即number[]数组的下标),最小数是正数,那么取反后就是负数了
                i=200-i;//纠正i的位置是为了下面求和遍历能够正确,这是一个坑
            }

出现的错误:

 

画图理解 

 134. 加油站

贪心算法(方法一)

直接从全局进行贪心选择,情况如下:

  • 情况一:如果gas的总和小于cost总和,那么无论从哪里出发,一定是跑不了一圈的

  • 情况二:rest[i] = gas[i]-cost[i]为一天剩下的油,i从0开始计算累加到最后一站,如果累加没有出现负数,说明从0出发,油就没有断过,那么0就是起点。

  • 情况三:如果累加的最小值是负数,汽车就要从非0节点出发,从后向前,看哪个节点能把这个负数填平,能把这个负数填平的节点就是出发节点。

代码实现

class Solution {
    public int canCompleteCircuit(int[] gas, int[] cost) {
        //你从其中的一个加油站出发,开始时油箱为空。如果你可以按顺序绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1 。如果存在解,则 保证 它是 唯一
        int curSum=0;
        int min=INT_MAX;//从起点出发,邮箱里的油量最小值
        for(int i=0;i<gas.length;i++){
            int rest=gas[i]-cost[i];
            curSum+=rest;
            if(curSum<min){
                min=curSum;//对例子:-2,-2,-2,3,3来说,得到的min是-6
            }
        }
        if(curSum<0){return -1;}//如果gas的总和小于cost总和,那么无论从哪里出发,一定是跑不了一圈的
        if(min>=0){return 0;}//rest[i] = gas[i]-cost[i]为一天剩下的油,i从0开始计算累加到最后一站,如果累加没有出现负数,说明从0出发,油就没有断过,那么0就是起点。题目说了起点唯一
        for(int i=gas.length-1;i>=0;i--){//如果累加的最小值是负数,汽车就要从非0节点出发,从后向前,看哪个节点能把这个负数填平,能把这个负数填平的节点就是出发节点。
            int rest=gas[i]-cost[i];
            min+=rest;
            if(min>=0){return i;}

        }
        return -1;
    }
}

贪心算法(方法二)

思路一:首先如果总油量减去总消耗大于等于零那么一定可以跑完一圈,说明 各个站点的加油站剩油量rest[i]相加一定是大于等于零的。

每个加油站的剩余量rest[i]为gas[i] - cost[i]。

i从0开始累加rest[i],和记为curSum,一旦curSum小于零,说明[0, i]区间都不能作为起始位置,因为这个区间选择任何一个位置作为起点,到i这里都会断油,那么起始位置从i+1算起,再从0计算curSum。

那么局部最优:当前累加rest[i]的和curSum一旦小于0,起始位置至少要是i+1,因为从i之前开始一定不行。全局最优:找到可以跑一圈的起始位置

方法1-代码实现

class Solution {
    public int canCompleteCircuit(int[] gas, int[] cost) {
        //你从其中的一个加油站出发,开始时油箱为空。如果你可以按顺序绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1 。如果存在解,则 保证 它是 唯一
        int curSum=0;
        int min=INT_MAX;//从起点出发,邮箱里的油量最小值
        for(int i=0;i<gas.length;i++){
            int rest=gas[i]-cost[i];
            curSum+=rest;
            if(curSum<min){
                min=curSum;//对例子:-2,-2,-2,3,3来说,得到的min是-6
            }
        }
        if(curSum<0){return -1;}//如果gas的总和小于cost总和,那么无论从哪里出发,一定是跑不了一圈的
        if(min>=0){return 0;}//rest[i] = gas[i]-cost[i]为一天剩下的油,i从0开始计算累加到最后一站,如果累加没有出现负数,说明从0出发,油就没有断过,那么0就是起点。题目说了起点唯一
        for(int i=gas.length-1;i>=0;i--){//如果累加的最小值是负数,汽车就要从非0节点出发,从后向前,看哪个节点能把这个负数填平,能把这个负数填平的节点就是出发节点。
            int rest=gas[i]-cost[i];
            min+=rest;
            if(min>=0){return i;}

        }
        return -1;
    }
}



思路2:如果总油量减去总消耗大于等于0那么一定可以跑完一圈,说明各个站点的加油站剩油量rest[i]相加一定是大于等于0的

每个加油站的剩余量rest[i]=gas[i]-cost[i]

从0开始累加rest[i],和记为curSum,一旦curSum小于0,说明[0,i]区间不能作为起始位置,因为这个区间选择任何一个位置作为起点,到i这里都会断油,那么起始位置从i+1算起,再从0计算curSum

代码实现 

class Solution {
    public int canCompleteCircuit(int[] gas, int[] cost) {
        int curSum=0;
        int totalSum=0;
        int start=0;
        for(int i=0;i<gas.length;i++){
            int rest=gas[i]-cost[i];
            curSum+=rest;
            totalSum+=rest;//计算所有的rest[i]
            if(curSum<0){
                start=i+1;//起始位置更新为i+1
                curSum=0;
            }
        }
        if(totalSum<0){return -1;}
        return start;

    }
}

135.分发糖果 (需要二刷)

135. 分发糖果

要比较相邻的两个元素,我们分两种情况来看

例子:1 2 2 5 4 3 2

从左向右看,只看右孩子比左孩子大的,得到的分发糖果数组是:

1 2 1 2 1 1 1 

从左向右看,只看左孩子比右孩子大的,得到的分发糖果数组是:

1 1 1 2 2 2 1(这是错误的遍历,应该要从右往左看),正确的:

从右向左看,只看左子比右孩子大的,得到的分发糖果数组是:

1 1 1 4 3 2 1

是非常巧妙的贪心思路,不要同时考虑某个数的左边和右边,容易顾此失彼,应该一侧一侧考虑

class Solution {
    public int candy(int[] ratings) {
      
        int[] candyNum=new int[ratings.length];//原来记录每个人的通过数量
        candyNum[0] = 1;
//先从左到右遍历一遍,找出右边比左边大的
        for(int i=0;i<ratings.length-1;i++){
            if(ratings[i]<ratings[i+1]){
                candyNum[i+1]=candyNum[i]+1;   
            }
            else{
                 candyNum[i+1]=1;
            }
    
           
        }
      //  再从右往左遍历一遍,找出左边比右边大的
      for(int i=ratings.length-1;i>0;i--){
          if(ratings[i-1]>ratings[i]){
              candyNum[i-1]=Math.max(candyNum[i-1],candyNum[i]+1);
              
          }
          
      }
      int sum=Arrays.stream(candyNum).sum();
     return sum;

    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值