代码随想录算法训练营 || 贪心算法 1005 134 135

Day29

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

力扣题目链接

  • 给定一个整数数组 A,我们只能用以下方法修改该数组:我们选择某个索引 i 并将 A[i] 替换为 -A[i],然后总共重复这个过程 K 次。(我们可以多次选择同一个索引 i。)

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

  • 输入:A = [2,-3,-1,5,-4], K = 2

  • 输出:13

  • 解释:选择索引 (1, 4) ,然后 A 变为 [2,3,-1,5,4]。

思路

  • 我们可以先把数组按照绝对值从大到小进行排序

  • [5,-4,-3,2,-1],对这个数组,从前往后进行遍历,遇到负数就变为相反数,并把k--;循环结束条件是遍历到数组末尾或k=0

  • 如果k=0跳出循环,那就可以直接返回改变后数组的和

  • 如果数组遍历结束跳出循环,这时k还大于零

  • 如果k是偶数,那其实不用管了,取反两次还是自己

  • 如果k是奇数,就把数组最后一位的元素取反,因为它的绝对值最小,取反后让sum变小的最少

代码

class Solution {
    public int largestSumAfterKNegations(int[] nums, int k) {
        nums = IntStream.of(nums)
                .boxed()
                .sorted((o1, o2) -> Math.abs(o2) - Math.abs(o1))
                .mapToInt(Integer::intValue).toArray();//把数组按绝对值大小进行排序
        for (int i = 0; i < nums.length && k > 0; i++) {//循环结束条件是遍历结束或k为0了
            if (nums[i] < 0) {
                nums[i] = -nums[i];//把绝对值最大的负数取反
                k--;//处理k
            }
        }
        if (k % 2 == 1) nums[nums.length - 1] = -nums[nums.length - 1];//k为偶数不用管,为奇数就把绝对值最小的正数取反
        return Arrays.stream(nums).sum();
    }
}

134. 加油站

力扣题目链接

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

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

如果你可以绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1。

思路

  • 暴力遍历

  • 计算gas和cost数组的差数组,对这个arr进行遍历

  • 如果arr[i]小于0,那直接continue,这个加油站的油跑不到下一个加油站(能跑到上一个加油站么,不需要考虑,因为可以看上一个加油站能不能跑到这个加油站)

  • 如果大于0,那就从这里开始循环,跑一圈(取余操作),如果跑的过程中haveGas小于零,那从这个加油站开始就跑不了一圈,结束内层循环;如果发现能跑一圈,那直接返回i

  • 最后外层循环结束,每个加油站都看完了还没有找到能跑一圈的,返回-1;

  • 比较巧妙的解法

  • 还是先给出rest数组

  • 对rest数组进行遍历,计算数组前i个元素的和,并计算部分和的最小值

  • 如果遍历结束,数组所有元素和加起来小于0,说明肯定跑不到,返回-1

  • 如果部分和的最小值都大于零,那随便跑,返回0

  • 剩下的情况就是要研究从哪个加油站开始跑的,我们这时倒着开始遍历,找恰好能填平最小部分和的元素位置

  • 贪心算法

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

  • 首先需要知道,rest数组累加如果不小于0,那就一定能跑玩

  • 对rest数组进行遍历,计算部分和,如果发现计算到i,部分和为负数,那其实从0到i这一段,不可能能作为起点,那就从i+1开始继续计算部分和,部分和从0开始重新计算

  • 如果遍历结束,累加小于0,跑不完返回-1

  • 否则一定能跑完,其实就是部分和大于零的初始值,用index进行记录

代码

class Solution {
    public int canCompleteCircuit(int[] gas, int[] cost) {
        int[] arr = new int[cost.length];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = gas[i] - cost[i];//计算arr数组
        }
        for (int i = 0; i < cost.length; i++){
            if (arr[i] < 0) continue;//小于0,跑不到下一个加油站,直接continue
            int index = i;//记录i
            int haveGas = arr[i];//记录这个加油站的油量
            while (haveGas >= 0){//haveGas小于零了,说明跑不够一圈,进行下一次外层循环
                index = (index + 1) % arr.length;//注意是循环,最后一位的下一位是第一位
                haveGas += arr[index];//不断更新油量,可能多也可能少
                if (index == i) return i;//如果跑了一圈了,直接返回i
            }
        }
        return -1;//外层循环结束了都没有返回,返回-1
    }
}

class Solution {
    public int canCompleteCircuit(int[] gas, int[] cost) {
        int[] rest = new int[gas.length];
        int sum = 0;
        int min = 0;
        for (int i = 0; i < rest.length; i++) {
            rest[i] = gas[i] - cost[i];
            sum += rest[i];//计算部分和
            min = Math.min(min,sum);//计算最小部分和
        }
        if (sum < 0) return -1;//油量不够耗油量,肯定跑不完,这里其实是剪枝操作,这一步也可以省去,填不平就返回-1
        if (min == 0) return 0;//最小部分和是0.没更新过,那随便跑
        for (int i = rest.length - 1; i >= 0;i--){//倒着遍历
            min += rest[i];//不断加上油量
            if (min >= 0) return i;//恰好填平,返回i
        }
        return -1;//其实不会执行到这里,但还是要返回,因为只要sum >= 0,就一定有办法让跑完
    }
}

class Solution {
    public int canCompleteCircuit(int[] gas, int[] cost) {
        int curSum = 0;
        int totalSum = 0;
        int index = 0;
        for (int i = 0; i < gas.length; i++) {
            curSum = curSum + gas[i] - cost[i];
            totalSum = totalSum + gas[i] - cost[i];
            if (curSum < 0){//部分和小于零
                curSum = 0;//从0开始重新计算
                index = i + 1;//index从i+1开始
            }
        }
        if (totalSum < 0) return -1;//数组加和小于零一定跑不完,否则一定跑的完
        return index;//从index开始能跑完
    }
}

135. 分发糖果

力扣题目链接

老师想给孩子们分发糖果,有 N 个孩子站成了一条直线,老师会根据每个孩子的表现,预先给他们评分。

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

  • 每个孩子至少分配到 1 个糖果。

  • 相邻的孩子中,评分高的孩子必须获得更多的糖果。

那么这样下来,老师至少需要准备多少颗糖果呢?

示例 1:

  • 输入: [1,0,2]

  • 输出: 5

  • 解释: 你可以分别给这三个孩子分发 2、1、2 颗糖果。

示例 2:

  • 输入: [1,2,2]

  • 输出: 4

  • 解释: 你可以分别给这三个孩子分发 1、2、1 颗糖果。第三个孩子只得到 1 颗糖果,这已满足上述两个条件。

思路

  • 需要遍历两次,一次不好考虑

  • 先从左向右遍历,如果右边比左边分高,那就是左边的糖果+1,其他情况都给一个糖果

  • 然后从右向左遍历,如果左边比右边分高,那就要更新糖果,取原来的和右边糖果+1最大的(注意一定要取最大的)

  • 最后计算数组元素的和即可

代码

class Solution {
    public int candy(int[] ratings) {
        int[] candyVec = new int[ratings.length];
        for (int i = 0; i < ratings.length; i++){//从左向右遍历
            if (i > 0 && ratings[i] > ratings[i - 1])//比左边大
                candyVec[i] = candyVec[i - 1] + 1;//左边的+1
            else {
                candyVec[i] = 1;//其他情况都给1个糖果
            }
        }
        for (int i = ratings.length - 1; i >= 0; i--){//从右向左遍历
            if (i < ratings.length - 1 && ratings[i] > ratings[i + 1])//比右边大
                candyVec[i] = Math.max(candyVec[i],candyVec[i + 1] + 1);//取原来的和右边糖果数+1更大的
        }
        int sum = 0;
        for (int candy : candyVec){
            sum += candy;
        }
        return sum;//返回candyVec数组元素的和
    }
}

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