【代码训练营】day33 | 1005.K次取反后最大化的数组和 & 134. 加油站 & 135. 分发糖果

所用代码 java

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

题目链接:K次取反后最大化的数组和 LeetCode 1005 - 简单

思路

先选负数,再选0,再选正数里面晓得 => 从小到大选,0多选。

本题蕴含两次贪心:

  • 优先对负数取反,负数里面优先对绝对值最大的取反
  • 都是正数取最小值,把k全部消耗掉
class Solution {
    public int largestSumAfterKNegations(int[] nums, int k) {
        int sum = 0;
        // 按绝对值大小排序 -- 这个太难了
        nums = IntStream.of(nums)
                .boxed()
                .sorted(((o1, o2) -> Math.abs(o2) - Math.abs(o1)))
                .mapToInt(Integer::intValue).toArray();
//        System.out.println(Arrays.toString(nums));
        // 此时数全是正的(k>0) 从大到小排序的
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] < 0 && k > 0) {
                nums[i] = -nums[i];
                k--;
            }
        }
        // 若k没翻转完,最后一个数肯定是最小的
        // k为奇数就把最小的翻转,偶数则翻转完不变
        if (k !=0 && k%2 == 1) {
            nums[nums.length - 1] = -nums[nums.length - 1];
        }for (int num : nums) {
            sum += num;
        }
        return sum;
    }
}

由于java按绝对值排序使用的int流IntStream有点复杂,所有我用简单的方法,就是两次排序。

  • 第一次贪心,从小到大排序,若是负数就取反
  • 第二次贪心,再从小到大排序,若还要取反就只用翻第一个数
class Solution {
    public int largestSumAfterKNegations(int[] nums, int k) {
        int sum = 0;
        Arrays.sort(nums);
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] < 0 && k > 0) {
                nums[i] = -nums[i];
                k--;
            }
        }
//        System.out.println(Arrays.toString(nums));
        // 两次排序,保证第一个数是最小的
        Arrays.sort(nums);
//        System.out.println(Arrays.toString(nums));
        // 若k没翻转完,第一个数肯定是最小的
        // k为奇数就把最小的翻转,偶数则翻转完不变
        if (k !=0 && k%2 == 1) {
            nums[0] = -nums[0];
        }for (int num : nums) {
            sum += num;
        }
        return sum;
    }
}

还有一种一次排序的方法

class Solution {
    public int largestSumAfterKNegations(int[] nums, int k) {
        if (nums.length == 1){
            return k % 2 == 0 ? nums[0] : -nums[0];
        }
        Arrays.sort(nums);
        int sum = 0;
        // 用来对nums计数
        int index = 0;
        for (int i = 0; i < k; i++) {
            // 判断是不是负数,是就取反
            if (i < nums.length - 1 && nums[index] < 0){
                nums[index] = -nums[index];
                // 若后一个数的绝对值比当前的数更小,就证明下一个数是待取反的数
                if (nums[index] >= Math.abs(nums[index + 1])){
                    index ++;
                }
                continue;
            }
            nums[index] = -nums[index];
        }for (int num : nums) {
            sum += num;
        }
        return sum;
    }
}

总结

本题需两次贪心,每次贪心都是取最小的值判断是否要取反,所有我就进行两次排序来操作。

加油站 LeetCode 134

题目链接:加油站 LeetCode 134 - 中等

思路

无。


局部最优:curSum为正,能往下一个站点跑

全局最优:找到可以跑完一圈的起点

注意: for循环适合模拟从头到尾的遍历,while循环适合模拟环形的遍历

暴力: 但是超时了!!

class Solution {
    public int canCompleteCircuit(int[] gas, int[] cost) {
        for (int i = 0; i < gas.length; i++) {
            // 记录当前剩余的油量
            int rest = gas[i] - cost[i];
            int start = (i + 1) % gas.length;
            // 模拟以 i 为起点行使一圈 rest>0则表示可以到下一个站点
            // 所以当剩余油量小于0或是回到起点了就退出
            // start == i 就证明回到了起点
            while (rest > 0 && start != i){
                rest += gas[start] - cost[start];
                start = (start + 1) % gas.length;
            }
            if (rest >=0 && start == i) return start;
        }
        return -1;
    }
}

贪心:

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++) {
            curSum += (gas[i] - cost[i]);
            totalSum += (gas[i] - cost[i]);
            // 当前可以行驶的路程不至于开往下一个站点,起始位置就是i+1
            // (i + 1) % gas.length 为了放在i在末尾的时候没取到第一个数
            if (curSum <= 0) {
                curSum = 0;
                start = (i + 1) % gas.length;
            }
        }
        if (totalSum < 0) return -1;
        return start;
    }
}

总结

按上述判断所剩的油是否可以开往下一站点,若不能开往下一站点了,起点一定是i+1。但是需要注意的是我们的总油量小于0,肯定是不能环路一周的。

本题还有一种解法:

class Solution {
    public int canCompleteCircuit(int[] gas, int[] cost) {
        // 当前油量和
        int curSum = 0;
        // 从起点出发的最小油量
        int min = Integer.MAX_VALUE;
        for (int i = 0; i < gas.length; i++) {
            int rest = gas[i] - cost[i];
            curSum += rest;
            if (curSum < min){
                min = curSum;
            }
        }// 1、当前油量和小于0,不能绕一圈
        if (curSum < 0) return -1;
        // 2、从起点出发的最小油量一直保持大于0,则从第一个起点出发肯定能绕一圈
        if (min >= 0) return 0;
        // 3、min < 0,在中途某个地方不能往下一站开
        for (int i = gas.length - 1; i >= 0; i--) {
            int rest = gas[i] - cost[i];
            // 由于本题是唯一解,我们从后往前遍历,
            // 看到哪个位置能把从起点出发的最小油量和给填满,
            // 该位置就是出发点
            min += rest;
            if (min >= 0){
                return i;
            }
        }
        return -1;
    }
}

分发糖果 LeetCode 135

题目链接:分发糖果 LeetCode 135 - 困难

思路

无。


主要有两种情况,然后取每次的最大值。

  1. 右边小孩比左边小孩得分高的情况 <
  2. 左边孩子比右边孩子得分高的情况 >
class Solution {
    public int candy(int[] ratings) {
        int len = ratings.length;
        int[] candyNum = new int[len];
        candyNum[0] = 1;
        // 左边<右边 的情况 从左到右遍历 -- 从第二个小孩开始考虑
        for (int i = 1; i < len; i++) {
            if (ratings[i] > ratings[i-1]){
                candyNum[i] = candyNum[i-1] + 1;
            }else {
                candyNum[i] = 1;
            }
        }// 左边>右边 的情况 从右到左遍历 -- 从倒数第二个小孩开始考虑
        for (int i = len - 2; i >= 0; i--) {
            if (ratings[i] > ratings[i + 1]){
                candyNum[i] = Math.max(candyNum[i], candyNum[i+1] + 1);
            }
        }// 统计最后的糖果数
        int sum = 0;
        for (int i : candyNum) {
            sum += i;
        }
        return sum;
    }
}

总结

这题有点难,题都没看懂,其实是这样的:

在这里插入图片描述

若左>右,左就要多一个糖果;同理右>左,右也要多一个糖果。

我们要同时考虑两把的糖果数谁更大。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值