1005.K次取反后最大化的数组和 134. 加油站 135. 分发糖果

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

力扣题目链接(opens new window)

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

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

示例 1:

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

示例 2:

  • 输入:A = [3,-1,0,2], K = 3
  • 输出:6
  • 解释:选择索引 (1, 2, 2) ,然后 A 变为 [3,1,0,2]。

示例 3:

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

提示:

  • 1 <= A.length <= 10000
  • 1 <= K <= 10000
  • -100 <= A[i] <= 100

思路:贪心

这道题目属于贪心简单题,不使用贪心算法也可以算出来。但是为了锻炼贪心算法的思维,这里使用贪心的思路
数组先进行升序排序,
首先对数组元素进行遍历,如果元素为负数,反转为整数,否则不做改动.每次反转将K–。
第一种情况 k <= nums.length
当k == 0 时,返回数组所有元素之和
第二种情况 k > nums.length
此时数组元素均 >= 0,那么找到数组中最小的元素,对最小元素k次转换即可
贪心算法局部最优解:获取当前元素最大值
贪心算法整体最优解:获取数组元素最大和

时间复杂度O(nlogn) :快速排序时间复杂度O(nlogn)
空间复杂度: O(logN): java所使用的内置函数用的是快速排序需要 logN 的空间

代码如下

public int largestSumAfterKNegations(int[] nums, int k) {
    if (nums == null || nums.length == 0 || k <= 0)
        return 0;
    Arrays.sort(nums);


    int minIndex = 0;// 数组最小元素下标
    int minEle = Integer.MAX_VALUE;// 数组最小元素value
    for (int i = 0; i < nums.length; i++) {
        if (nums[i] < 0) { // 当前元素为负数,转换为整数
            nums[i] = -nums[i];
            k--;
        }

        if (k == 0) {// 终止条件
            return Arrays.stream(nums).sum();
        }

        if (nums[i] < minEle) {// 获取反转后数组中,最小的元素和元素下标
            minEle = nums[i];
            minIndex = i;
        }
    }

    // 此时k > 0,数组元素均大于0。需要找到数组中最小的元素,并反转k次
    for (int i = 0; i < k; i++) {
        nums[minIndex] = -nums[minIndex];
    }
    // 返回数组总和
    return Arrays.stream(nums).sum();


}

134.加油站

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

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

如果你可以绕环路行驶一周,则返回出发时加油站的编号,否则返回 -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 升汽油。因此,无论怎样,你都不可能绕环路行驶一周。

思路:暴力

虽然这是暴力算法,但我就是按照贪心算法的思路去写出来的
贪心算法局部最优解:到达下一处加油站时,油箱的油 >= 0
贪心算法整体最优解:环路一圈,邮箱的油 >= 0
代码超时了,代码随想录同样方式的暴力方法也超时了

时间复杂度: O(n2)
空间复杂度: O(1)

代码如下:

public static int canCompleteCircuit(int[] gas, int[] cost) {
    int sum = 0;
    for (int i = 0; i < gas.length; i++) {// gas数组元素作为起点,依次遍历。若遍历过程中和遍历结束后,sum均 >= 0,则返回当前索引
        int length = gas.length;
        int j = i;

        sum = gas[j];// 从j号加油站出发
        while (length > 0) {

            sum = sum - cost[j];// 路径消耗后油量
            if (sum < 0)// 若路径小于0,跳出while循环,继续for循环
                break;

            if (j == gas.length - 1) {// 对数组边缘条件处理
                j = 0;
            } else {
                j++;
            }

            sum = sum + gas[j];// 到达目的地补充后的油量
            length--;
        }

        if (sum >= 0)
            return i;

    }
    return -1;
}

思路:贪心

暴力算法时间复杂度过高
如果总油量大于总油耗,那么总会有一条路线可以完整走完所有的加油站
如果总油量小于总油耗,那么无法走完所有加油站
单个加油站的剩余油量gas[i] - cost[i]
定义当前剩余油量为curSum,从i = 0开始计算每个加油站的剩余油量
一旦当前剩余油量curSum < 0,那么说明[0,i]区间不适合做为起始位置
因为在这个区间任意加油站出发,中途都会没油
那么起始位置从i+1算起,再从0计算curSum。
如果算完的curSum > 0,注意此时还有一种可能,那就是整个总油量减去总消耗小于0,totalSum = totalSum + gas[i] - cost[i];记录每个加油站的剩余油量
若totalSum < 0,返回-1.
如图所示
在这里插入图片描述

局部最优解:一旦curSum < 0,从i+1开始寻找位置
全局最优解:找到适合出发的起始位置

时间复杂度: O(n)
空间复杂度: O(1)

代码如下

public static int canCompleteCircuit(int[] gas, int[] cost) {

    int startPosition = 0;// 适合出发的起始位置
    int curSum = 0;// 当前的剩余油量
    int totalSum = 0;// 走过的所有加油站剩余油量
    for (int i = 0; i < gas.length; i++) {
        curSum = curSum + gas[i] - cost[i];
        totalSum = totalSum + gas[i] - cost[i];//

        if (curSum < 0) {// 那么说明[0,i]区间不适合做为起始位置
            curSum = 0;
            startPosition = i + 1;
        }
    }

    if (totalSum < 0) {// 没有合适的路线
        return -1;
    }
    return startPosition;
}

135.分发糖果

力扣题目链接(opens new window)

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

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

  • 每个孩子至少分配到 1 个糖果。
  • 相邻的孩子中,评分高的孩子必须获得更多的糖果。

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

示例 1:

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

示例 2:

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

思路:贪心

题目要求1.每个孩子至少分配到 1 个糖果2.相邻的孩子中,评分高的孩子必须获得更多的糖果。
那么如果我们同时对一个孩子的左右相邻的孩子做比较,部分孩子会得不到应有的糖
需要对孩子的左右相邻孩子分别作比较
1.当前孩子和左孩子做比较,如果比左孩子大,那么当前孩子的糖加1。for循环从前到后
2.当前孩子和右孩子作比较,如果比右孩子大,那么当前孩子的糖为【右孩子糖数量+1和当前孩子糖数量】最大值。for循环从后到前
candy[i]只有取最大的才能既保持对左边candy[i - 1]的糖果多,也比右边candy[i + 1]的糖果多。

这里需要注意一个很重要的点,就是For循环遍历的顺序不要搞错
遍历顺序这里有同学可能会有疑问,当前孩子和右孩子作比较,为什么不能从前向后遍历呢?
因为 rating[5]与rating[4]的比较 要利用上 rating[5]与rating[6]的比较结果,所以 要从后向前遍历。
如果从前向后遍历,rating[5]与rating[4]的比较 就不能用上 rating[5]与rating[6]的比较结果了 。如图:
所以确定当前孩子和右孩子作比较的情况一定要从后向前遍历!
同理 当前孩子和左孩子做比较 。for循环不能从后到前遍历

for循环从前到后:当前孩子和左孩子做比较
贪心局部最优解:当前孩子比左孩子大,那么就比左孩子的糖果多一个
for循环从后到前:当前孩子和右孩子做比较
贪心局部最优解:当前孩子和右孩子作比较,如果比右孩子大,那么当前孩子的糖为【右孩子糖数量+1和当前孩子糖数量】最大值。
贪心算法整体最优解:得到最少的糖果数量

代码如下

public int candy(int[] ratings) {
    if (ratings == null || ratings.length == 0)
        return 0;

    int candySum = 0;
    // 定义数组存储每个孩子的糖果数量。数组默认值为1
    int[] candy = new int[ratings.length];
    for (int i = 0; i < ratings.length; i++) {
        candy[i] = 1;
    }

    for (int i = 1; i < ratings.length; i++) {// 当前孩子和左孩子做比较,如果比左孩子大,那么当前孩子的糖加1。for循环从前到后
        if (ratings[i] > ratings[i - 1]) {
            candy[i] = candy[i - 1] + 1;
        }

    }

    for (int i = ratings.length - 2; i >= 0; i--) {// 当前孩子和右孩子作比较,如果比右孩子大,那么当前孩子的糖为【右孩子糖数量+1和当前孩子糖数量】最大值。for循环从后到前
        if (ratings[i] > ratings[i + 1]) {
            candy[i] = Math.max(candy[i + 1] + 1,candy[i]);
        }
    }


    // 返回糖果总数
    for (int i = 0; i < ratings.length; i++) {
        candySum = candySum + candy[i];
    }
    return candySum;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值