代码随想录算法训练营第三十五天| 1005. K 次取反后最大化的数组和、134. 加油站、135. 分发糖果、860. 柠檬水找零、406. 根据身高重建队列

[LeetCode] 1005. K 次取反后最大化的数组和
[LeetCode] 1005. K 次取反后最大化的数组和 文章解释

[LeetCode] 1005. K 次取反后最大化的数组和 视频解释

题目:

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

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

自己看到题目的第一想法

    首先要找到所有的负数,将负数全部取反。

    当负数全部取反后, 如果还需要继续取反, 则取负数的最大值和非负数的最小值中, 绝对值更小的那个数来取反。

    因此需要对数组按从小到大排序,然后从最小的负数开始取反。同时当遇到第一个非负数时候,记录住这个非负数和上一个数, 保存住绝对值更小的那个数的值。

    如果负数全部取反后, K 的值依旧有剩余, 并且不是偶数, 则对刚刚保存住更小的那个值取反。 这样最后累加下来的结果就是期望值。

看完代码随想录之后的想法

// 效率不高
class Solution {
    public int largestSumAfterKNegations(int[] nums, int k) {
        int result = 0;
        Integer smallestAbsoluteNumber = null;
        Arrays.sort(nums);
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] < 0 && k > 0) {
                nums[i] *= -1;
                k--;
            }
            result += nums[i];
            if (smallestAbsoluteNumber == null) {
                smallestAbsoluteNumber = nums[i];
            } else {
                smallestAbsoluteNumber = Math.min(nums[i], smallestAbsoluteNumber);
            }
        }
        if (k % 2 == 1) {
            result -= 2 * smallestAbsoluteNumber;
        }
         return result;
    }
}
class Solution {
    public int largestSumAfterKNegations(int[] nums, int k) {
        int result = 0;

        // 这个排序效率太低了
        // nums = IntStream.of(nums)
        //         .boxed()
        //         .sorted((o1, o2) -> Math.abs(o2) - Math.abs(o1))
        //         .mapToInt(Integer::intValue).toArray();
        // 替换成快速排序会好很多
        sort(nums, 0, nums.length - 1);

        for (int i = 0; i < nums.length; i++) {
            if (nums[i] < 0 && k > 0) {
                nums[i] *= -1;
                k--;
            }
            result += nums[i];
        }
        if (k % 2 == 1) {
            result -= 2 * nums[nums.length - 1];
        }
        return result;
    }
    private void sort(int[] nums, int start, int end) {
        if (start >= end) {
            return;
        }
        int pivot = doSort(nums, start, end);
        sort(nums, start, pivot - 1);
        sort(nums, pivot + 1, end);
    }
    private int doSort(int[] nums, int start, int end) {
        int pivot = start;
        while (start < end) {
            while (start < end && Math.abs(nums[end]) <= Math.abs(nums[pivot])) {
                end--;
            }
            while (start < end && Math.abs(nums[start]) >= Math.abs(nums[pivot])) {
                start++;
            }
            if (start < end) {
                int temp = nums[start];
                nums[start] = nums[end];
                nums[end] = temp;
            }
        }
        int temp = nums[pivot];
        nums[pivot] = nums[start];
        nums[start] = temp;
        return start;
    }
}

自己实现过程中遇到哪些困难

    对数组中的元素按照绝对值大小排序, 因为题目中传入的是 int[] 数组, 因此没办法直接使用 Arrays.sort() 方法。看随想录的题解中使用了 IntStream.sorted , 一个是不太熟悉, mapToInt(Integer::intValue) 这部分实在是不了解什么意思。 一个是效率比较低, 因此自己实现了一下快速排序。

    快速排序一下子就为难到我了, 费了很大力气才弄出来。

    哎。。。基础太不扎实了。

[LeetCode] 134. 加油站

[LeetCode] 134. 加油站 文章解释

[LeetCode] 134. 加油站 视频解释

题目:

在一条环路上有 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

[LeetCode] 134. 加油站

自己看到题目的第一想法

    for...for 暴力肯定不可以啦。

    先要计算出每个站点出发后到下一站剩余的油的数量。同时要从非负数开始累加,累加和一遇到负数就要从下一个站点开始重新计算。 但是什么时候终止呢?看来还是要先用 for 来遍历所有站点, 这一层 for 循环表示从哪个站点开始。 

    思绪凌乱了。。。

看完代码随想录之后的想法

    米奇吃着妙脆角到妙妙屋一样的妙!

// 暴力解法:超出时间限制
class Solution {
    public int canCompleteCircuit(int[] gas, int[] cost) {
        for (int i = 0; i < gas.length; i++) {
            int rest = gas[i] - cost[i];
            if (rest < 0) {
                continue;
            }
            int index = (i + 1) % gas.length;
            while (index != i) {
                rest += gas[index] - cost[index];
                if (rest < 0) {
                    break;
                }
                index = (index + 1) % gas.length;
            }
            if (index == i) {
                return i;
            }
        }
        return -1;
    }
}
class Solution {
    public int canCompleteCircuit(int[] gas, int[] cost) {
        int rest = 0;
        int sum = 0;
        for (int i = 0; i < gas.length; i++) {
            sum += gas[i] - cost[i];
            rest = Math.min(rest, sum);
        }
        if (sum < 0) {
            return -1;
        }
        if (rest >= 0) {
            return 0;
        }
        sum = 0;
        // 因为汽车必须顺时针走, 因此如果存在某个点开始出发能绕一圈的话,
        // 从该点走到结尾后开到第一个油站时,一定是不会缺油的。
        // 同时从第一个油站到出发油站缺的最大的油一定要小于从该点走到结尾后开到第一个油站时剩余的油
        // 因此如果从最后一个站点往前,一直积累油耗,
        // 如果到某个站点时,累积的油耗比全程走下来最大缺油数。 
        // 则表示从该站点出发绕一圈可以回到该站点。
        for (int i = gas.length - 1; i >= 0; i--) {
            sum += gas[i] - cost[i];
            if (sum + rest >= 0) {
                return i;
            }
        }
        return -1;
    }
}

class Solution {
    public int canCompleteCircuit(int[] gas, int[] cost) {
        int sum = 0;
        int rest = 0;
        int start = 0;
        for (int i = 0; i < gas.length; i++) {
            int remain = gas[i] - cost[i];
            rest += remain;
            sum += remain;
            if (rest < 0) {
                rest = 0;
                start = i + 1;
            }
        }
        if (sum >= 0) {
            return start;
        }
        return -1;
    }
}

自己实现过程中遇到哪些困难

    无

[LeetCode] 135. 分发糖果

[LeetCode] 135. 分发糖果 文章解释

[LeetCode] 135. 分发糖果 视频解释

题目:

n 个孩子站成一排。给你一个整数数组 ratings 表示每个孩子的评分。

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

  • 每个孩子至少分配到 1 个糖果。
  • 相邻两个孩子评分更高的孩子会获得更多的糖果。

请你给每个孩子分发糖果,计算并返回需要准备的 最少糖果数目 。

示例 1:

输入:ratings = [1,0,2]
输出:5
解释:你可以分别给第一个、第二个、第三个孩子分发 2、1、2 颗糖果。

示例 2:

输入:ratings = [1,2,2]
输出:4
解释:你可以分别给第一个、第二个、第三个孩子分发 1、2、1 颗糖果。
     第三个孩子只得到 1 颗糖果,这满足题面中的两个条件。

提示:

  • n == ratings.length
  • 1 <= n <= 2 * 10^4
  • 0 <= ratings[i] <= 2 * 10^4

[LeetCode] 135. 分发糖果

自己看到题目的第一想法

    遍历数组, 每个节点判断左边和右边的值,大于的话就置为 2 ,否则为 1。 真的听简单的。

    运行后才知道, 右边节点可能比右边节点的右边节点大, 这时候右边节点会为 2, 这样当前节点就不满足比相邻节点多一个糖果的要求了。

    如果右边节点调整了, 再调整左边节点, 这样似乎太复杂太麻烦了。

看完代码随想录之后的想法

    好!简单!

class Solution {
    public int candy(int[] ratings) {
        int[] candies = new int[ratings.length];
        candies[0] = 1;
        for (int i = 1; i < ratings.length; i++) {
            if (ratings[i - 1] < ratings[i]) {
                candies[i] = candies[i - 1] + 1;
            } else {
                candies[i] = 1;
            }
        }

        int result = candies[candies.length - 1];
        
        for (int i = candies.length - 2; i >= 0; i--) {
            if (ratings[i] > ratings[i + 1]) {
                candies[i] = Math.max(candies[i], candies[i + 1] + 1);
            }
        
            result += candies[i];
        }
        return result;
    }
}

自己实现过程中遇到哪些困难

    比较糖果的时候,一定要以最左边或者最右边作为对比的基准。 而不能拿最左边(也就是第0位)和第一位对比, 再拿第一位和第二位对比。 因为只有左右两边是只被单边影响的。 一开始没掌握这个关键点 ,导致看完解释还是实现得有问题。

[LeetCode] 860. 柠檬水找零

[LeetCode] 860. 柠檬水找零 文章解释

[LeetCode] 860. 柠檬水找零 视频解释

题目:

在柠檬水摊上,每一杯柠檬水的售价为 5 美元。顾客排队购买你的产品,(按账单 bills 支付的顺序)一次购买一杯。

每位顾客只买一杯柠檬水,然后向你付 5 美元、10 美元或 20 美元。你必须给每个顾客正确找零,也就是说净交易是每位顾客向你支付 5 美元。

注意,一开始你手头没有任何零钱。

给你一个整数数组 bills ,其中 bills[i] 是第 i 位顾客付的账。如果你能给每位顾客正确找零,返回 true ,否则返回 false 。

示例 1:

输入:bills = [5,5,5,10,20]
输出:true
解释:
前 3 位顾客那里,我们按顺序收取 3 张 5 美元的钞票。
第 4 位顾客那里,我们收取一张 10 美元的钞票,并返还 5 美元。
第 5 位顾客那里,我们找还一张 10 美元的钞票和一张 5 美元的钞票。
由于所有客户都得到了正确的找零,所以我们输出 true。

示例 2:

输入:bills = [5,5,10,10,20]
输出:false
解释:
前 2 位顾客那里,我们按顺序收取 2 张 5 美元的钞票。
对于接下来的 2 位顾客,我们收取一张 10 美元的钞票,然后返还 5 美元。
对于最后一位顾客,我们无法退回 15 美元,因为我们现在只有两张 10 美元的钞票。
由于不是每位顾客都得到了正确的找零,所以答案是 false。

提示:

  • 1 <= bills.length <= 10^5
  • bills[i] 不是 5 就是 10 或是 20 

[LeetCode] 860. 柠檬水找零

自己看到题目的第一想法

    收到五块和十块的就记录下来。 每次找零钱的时候先用十块的找,不够用了再用五块的。五块的都没办法找零的话,就失败。

看完代码随想录之后的想法

    一样。

class Solution {
    public boolean lemonadeChange(int[] bills) {
        int fiveCount = 0;
        int tenCount = 0;
        for (int i = 0; i < bills.length; i++) {
            if (bills[i] == 5) {
                fiveCount++;
            } else if (bills[i] == 10) {
                tenCount++;
                fiveCount--;
            } else {
                if (tenCount > 0) {
                    tenCount--;
                    fiveCount--;
                } else {
                    fiveCount -= 3;
                }
            }
            if (fiveCount < 0) {
                return false;
            }
        }
        return true;
    }
}

自己实现过程中遇到哪些困难

[LeetCode] 406. 根据身高重建队列
[LeetCode] 406. 根据身高重建队列 文章解释

[LeetCode] 406. 根据身高重建队列 视频解释

题目:

假设有打乱顺序的一群人站成一个队列,数组 people 表示队列中一些人的属性(不一定按顺序)。每个 people[i] = [hi, ki] 表示第 i 个人的身高为 hi ,前面 正好 有 ki 个身高大于或等于 hi 的人。

请你重新构造并返回输入数组 people 所表示的队列。返回的队列应该格式化为数组 queue ,其中 queue[j] = [hj, kj] 是队列中第 j个人的属性(queue[0] 是排在队列前面的人)。

示例 1:

输入:people = [[7,0],[4,4],[7,1],[5,0],[6,1],[5,2]]
输出:[[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]]
解释:
编号为 0 的人身高为 5 ,没有身高更高或者相同的人排在他前面。
编号为 1 的人身高为 7 ,没有身高更高或者相同的人排在他前面。
编号为 2 的人身高为 5 ,有 2 个身高更高或者相同的人排在他前面,即编号为 0 和 1 的人。
编号为 3 的人身高为 6 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。
编号为 4 的人身高为 4 ,有 4 个身高更高或者相同的人排在他前面,即编号为 0、1、2、3 的人。
编号为 5 的人身高为 7 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。
因此 [[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]] 是重新构造后的队列。

示例 2:

输入:people = [[6,0],[5,0],[4,0],[3,2],[2,2],[1,4]]
输出:[[4,0],[5,0],[2,2],[3,2],[1,4],[6,0]]

提示:

  • 1 <= people.length <= 2000
  • 0 <= hi <= 10^6
  • 0 <= ki < people.length
  • 题目数据确保队列可以被重建

[LeetCode] 406. 根据身高重建队列

自己看到题目的第一想法

    [暴力解法] 身高从低到高,身高相同就按比自己高的人数量从低到高排。排序后从头到尾遍历数组,将对应的人插刀正确的位置上。每插入一个就要从头重新遍历一次,直到遍历的末尾。

class Solution {
    public int[][] reconstructQueue(int[][] people) {
        // [[5,0],[5,2],[6,1],[7,0],[7,1]]

        Arrays.sort(people,new Comparator<int[]>(){
            @Override
            public int compare(int[] a,int[] b){
                if (a[0] == b[0]) {
                    return a[1] - b[1];
                } else {
                    return a[0] - b[0];
                }
            }
        });
        // 5 0, 5 2, 6 1, 7 0, 4 4, 7 1
        int index = 0;
        int count = 0;
        while (index < people.length) {
            count = people[index][1];
            for (int i = 0; i < index && count > 0; i++) {
                if (people[i][0] >= people[index][0]) {
                    count--;
                }
            }
            if (count > 0) {
                for (int j = index + 1; j < people.length && count > 0; j++) {
                    if (people[index][0] <= people[j][0]) {
                        int[] temp = people[j];
                        people[j] = people[index];
                        people[index] = temp;
                        index = j;
                        count--;
                    }
                }
                index = 0;
            } else {
                index++;
            }
        }
        return people;
    }
}    

看完代码随想录之后的想法

    身高从高到低,比自己高的人的数量从低到高,这样的话从头往后遍历,把当前人插入到正确的位置就可以了。因为在自己前面的都是比自己高的,需要几个高的人,就插入到对应的人后面即可。好多了!!!

class Solution {
    public int[][] reconstructQueue(int[][] people) {
        Arrays.sort(people,new Comparator<int[]>(){
            @Override
            public int compare(int[] a, int[] b){
                if (a[0] == b[0]) {
                    return a[1] - b[1];
                } else {
                    return b[0] - a[0];
                }
            }
        });
        for (int i = 0; i < people.length; i++) {
            if (people[i][1] != i) {
                int index = i;
                while (index != i) {
                    int[] temp = people[i];
                    people[i] = people[i - 1];
                    people[i - 1] = temp;                      
                }
            }
        }
        return people;
    }
}
// 再优化一下
class Solution {
    public int[][] reconstructQueue(int[][] people) {
        Arrays.sort(people,new Comparator<int[]>(){
            @Override
            public int compare(int[] a, int[] b){
                if (a[0] == b[0]) {
                    return a[1] - b[1];
                } else {
                    return b[0] - a[0];
                }
            }
        });
        LinkedList<int[]> result = new LinkedList<>();// 注意这里 LinkedList 的使用。 妙妙妙!
        for (int i = 0; i < people.length; i++) {
            result.add(people[i][1], people[i]);
        }
        return result.toArray(new int[people.length][2]);
    }
}

自己实现过程中遇到哪些困难

    思路错了一切都错了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值