代码随想录训练营day34| 1005.K次取反后最大化的数组和 134. 加油站 135. 分发糖果...

简介: 代码随想录训练营day34| 1005.K次取反后最大化的数组和 134. 加油站 135. 分发糖果...

前言

代码随想录算法训练营day34


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

1.题目

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

来源:力扣(LeetCode) 链接:https://leetcode.cn/problems/maximize-sum-of-array-after-k-negations

2.解题思路

方法一:从小到大修改每个负数

思路与算法

由于我们希望数组的和尽可能大,因此除非万不得已,我们应当总是修改负数,并且优先修改值最小的负数。因为将负数 −x−x 修改成 xx 会使得数组的和增加 2x2x,所以这样的贪心操作是最优的。

当给定的 KK 小于等于数组中负数的个数时,我们按照上述方法从小到大依次修改每一个负数即可。但如果 KK 的值较大,那么我们不得不去修改非负数(即正数或者 00)了。由于修改 00 对数组的和不会有影响,而修改正数会使得数组的和减小,因此:

1. 如果数组中存在 00,那么我们可以对它进行多次修改,直到把剩余的修改次数用完;
2. 
3. 如果数组中不存在 00 并且剩余的修改次数是偶数,由于对同一个数修改两次等价于不进行修改,因此我们也可以在不减小数组的和的前提下,把修改次数用完;
4. 
5. 如果数组中不存在 00 并且剩余的修改次数是奇数,那么我们必然需要使用单独的一次修改将一个正数变为负数(剩余的修改次数为偶数,就不会减小数组的和)。为了使得数组的和尽可能大,我们就选择那个最小的正数。
6. 
7. 需要注意的是,在之前将负数修改为正数的过程中,可能出现了(相较于原始数组中最小的正数)更小的正数,这一点不能忽略。

细节

为了实现上面的算法,我们可以对数组进行升序排序,首先依次遍历每一个负数(将负数修改为正数),再遍历所有的数(将 00 或最小的正数进行修改)。

然而注意到本题中数组元素的范围为 [−100,100][−100,100],因此我们可以使用计数数组(桶)或者哈希表,直接统计每个元素出现的次数,再升序遍历元素的范围,这样就省去了排序需要的时间。

3.代码实现

```java class Solution { public int largestSumAfterKNegations(int[] nums, int k) { Map freq = new HashMap (); for (int num : nums) { freq.put(num, freq.getOrDefault(num, 0) + 1); } int ans = Arrays.stream(nums).sum(); for (int i = -100; i < 0; ++i) { if (freq.containsKey(i)) { int ops = Math.min(k, freq.get(i)); ans += (-i) * ops * 2; freq.put(i, freq.get(i) - ops); freq.put(-i, freq.getOrDefault(-i, 0) + ops); k -= ops; if (k == 0) { break; } } } if (k > 0 && k % 2 == 1 && !freq.containsKey(0)) { for (int i = 1; i <= 100; ++i) { if (freq.containsKey(i)) { ans -= i * 2; break; } } } return ans; } }
```

二、Leetcode 134. 加油站

1.题目

在一条环路上有 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 升汽油。 因此,无论怎样,你都不可能绕环路行驶一周。

提示:

1. gas.length == n
2. cost.length == n
3. 1 <= n <= 105
4. 0 <= gas[i], cost[i] <= 104

2.解题思路

方法一:一次遍历

思路与算法

最容易想到的解法是:从头到尾遍历每个加油站,并检查以该加油站为起点,最终能否行驶一周。我们可以通过减小被检查的加油站数目,来降低总的时间复杂度。

假设我们此前发现,从加油站 xx 出发,每经过一个加油站就加一次油(包括起始加油站),最后一个可以到达的加油站是 yy(不妨设 x

∑i=xygas[i]<∑i=xycost[i]∑i=xjgas[i]≥∑i=xjcost[i] (For all j∈[x,y)) i=x∑ygas[i]

第一个式子表明无法到达加油站 yy 的下一个加油站,第二个式子表明可以到达 yy 以及 yy 之前的所有加油站。

现在,考虑任意一个位于 x,yx,y 之间的加油站 zz(包括 xx 和 yy),我们现在考察从该加油站出发,能否到达加油站 yy 的下一个加油站,也就是要判断 ∑i=zygas[i]∑i=zygas[i] 与 ∑i=zycost[i]∑i=zycost[i] 之间的大小关系。

根据上面的式子,我们得到:

∑i=zygas[i]=∑i=xygas[i]−∑i=xz−1gas[i]<∑i=xycost[i]−∑i=xz−1gas[i]<∑i=xycost[i]−∑i=xz−1cost[i]=∑i=zycost[i]i=z∑ygas[i]=i=x∑ygas[i]−i=x∑z−1gas[i]

其中不等式的第二步、第三步分别利用了上面的第一个、第二个不等式。

从上面的推导中,能够得出结论:从 x,yx,y 之间的任何一个加油站出发,都无法到达加油站 yy 的下一个加油站。

在发现了这一个性质后,算法就很清楚了:我们首先检查第 00 个加油站,并试图判断能否环绕一周;如果不能,就从第一个无法到达的加油站开始继续检查。

3.代码实现

```java class Solution { public int canCompleteCircuit(int[] gas, int[] cost) { int n = gas.length; int i = 0; while (i < n) { int sumOfGas = 0, sumOfCost = 0; int cnt = 0; while (cnt < n) { int j = (i + cnt) % n; sumOfGas += gas[j]; sumOfCost += cost[j]; if (sumOfCost > sumOfGas) { break; } cnt++; } if (cnt == n) { return i; } else { i = i + cnt + 1; } } return -1; } }
```

三、Leetcode 135. 分发糖果

1.题目

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

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

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

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

示例 1:

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

示例 2:

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

提示:

1. n == ratings.length
2. 1 <= n <= 2 * 104
3. 0 <= ratings[i] <= 2 * 104

来源:力扣(LeetCode) 链接:https://leetcode.cn/problems/candy

2.解题思路

方法一:两次遍历

思路及解法

我们可以将「相邻的孩子中,评分高的孩子必须获得更多的糖果」这句话拆分为两个规则,分别处理。

1. 左规则:当 ratings[i−1]<ratings[i]ratings[i−1]<ratings[i] 时,ii 号学生的糖果数量将比 i−1i−1 号孩子的糖果数量多。
2. 
3. 右规则:当 ratings[i]>ratings[i+1]ratings[i]>ratings[i+1] 时,ii 号学生的糖果数量将比 i+1i+1 号孩子的糖果数量多。

我们遍历该数组两次,处理出每一个学生分别满足左规则或右规则时,最少需要被分得的糖果数量。每个人最终分得的糖果数量即为这两个数量的最大值。

具体地,以左规则为例:我们从左到右遍历该数组,假设当前遍历到位置 ii,如果有 ratings[i−1]

在实际代码中,我们先计算出左规则 leftleft 数组,在计算右规则的时候只需要用单个变量记录当前位置的右规则,同时计算答案即可。

3.代码实现

```java class Solution { public int candy(int[] ratings) { int n = ratings.length; int[] left = new int[n]; for (int i = 0; i < n; i++) { if (i > 0 && ratings[i] > ratings[i - 1]) { left[i] = left[i - 1] + 1; } else { left[i] = 1; } } int right = 0, ret = 0; for (int i = n - 1; i >= 0; i--) { if (i < n - 1 && ratings[i] > ratings[i + 1]) { right++; } else { right = 1; } ret += Math.max(left[i], right); } return ret; } }
```
相关文章
|
4月前
|
存储 人工智能 算法
三维数组解决问题案例(天梯赛座位分配)
三维数组解决问题案例(天梯赛座位分配)
37 0
|
12天前
|
存储 算法
代码随想录算法训练营第五十九天 | LeetCode 739. 每日温度、496. 下一个更大元素 I
代码随想录算法训练营第五十九天 | LeetCode 739. 每日温度、496. 下一个更大元素 I
21 1
|
3月前
|
算法 搜索推荐 程序员
第四十六练 请以递归方式实现计算整数列表的和
第四十六练 请以递归方式实现计算整数列表的和
24 2
|
6月前
|
算法 网络架构
代码随想录算法训练营第三十三天 | LeetCode 1005. K 次取反后最大化的数组和、134. 加油站、135. 分发糖果
代码随想录算法训练营第三十三天 | LeetCode 1005. K 次取反后最大化的数组和、134. 加油站、135. 分发糖果
32 0
|
6月前
|
算法 Java
代码随想录算法训练营第三十四天 | LeetCode 860. 柠檬水找零、406. 根据身高重建队列、452. 用最少数量的箭引爆气球
代码随想录算法训练营第三十四天 | LeetCode 860. 柠檬水找零、406. 根据身高重建队列、452. 用最少数量的箭引爆气球
39 0
|
6月前
|
算法
代码随想录算法训练营第三十一天 | LeetCode 455. 分发饼干、376. 摆动序列、53. 最大子数组和
代码随想录算法训练营第三十一天 | LeetCode 455. 分发饼干、376. 摆动序列、53. 最大子数组和
37 0
|
7月前
|
算法
代码随想录Day28 贪心03 LeetCode T1005 K次取反后最大化的数组和 LeetCode T134 加油站 LeetCode T135 分发糖果
代码随想录Day28 贪心03 LeetCode T1005 K次取反后最大化的数组和 LeetCode T134 加油站 LeetCode T135 分发糖果
21 0
蓝桥 凑平方数 (我依旧很菜)
蓝桥 凑平方数 (我依旧很菜)
|
10月前
|
存储
L1-049 天梯赛座位分配 (20 分)( for循环的深入理解+三维数组+错误分析)
L1-049 天梯赛座位分配 (20 分)( for循环的深入理解+三维数组+错误分析)
105 0
|
11月前
|
机器学习/深度学习 算法 图计算
代码随想录训练营day59| 503.下一个更大元素II 42. 接雨水
代码随想录训练营day59| 503.下一个更大元素II 42. 接雨水