算法训练营(day34)

贪心算法理论基础

贪心的本质是选择每一阶段的局部最优,从而达到全局最优

举个例子,在规定次数内从一堆钞票里怎么拿面额最大的钞票,根据贪心算法的思想:每次拿最大的就好(够贪就行O(∩_∩)O)

贪心算法一般分为如下四步:

  • 将问题分解为若干个子问题
  • 找出适合的贪心策略
  • 求解每一个子问题的最优解
  • 将局部最优解堆叠成全局最优解

总而言之,贪心算法的本质其实就是 找到局部最优解,再比较所有的局部最优,得到全局最优

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

题目链接:https://leetcode.cn/problems/maximize-sum-of-array-after-k-negations/

给你一个整数数组 nums 和一个整数 k ,按以下方法修改该数组:

  • 选择某个下标 i 并将 nums[i] 替换为 -nums[i]

重复这个过程恰好 k 次。可以多次选择同一个下标 i 。以这种方式修改数组后,返回数组 可能的最大和

解题思路

解题过程:

思路一:贪心算法

  1. 排序

  2. 局部最优

    • 当有负数时,优先取反 绝对值较大负数

    • 没有负数时,优先取反 绝对值较小 的值

  3. 累加

详细代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Solution {
public int largestSumAfterKNegations(int[] nums, int k) {
if(nums.length == 1){
return k % 2 == 0 ? nums[0] : nums[1];
}
Arrays.sort(nums);
int sum = 0;
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++; //确保index指向的是全数组最小的数
}
continue;
}
nums[index] = -nums[index];
}

for(int num : nums){
sum += num;
}
return sum;
}
}

134. 加油站

题目链接:https://leetcode.cn/problems/gas-station/description/

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

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

给定两个整数数组 gascost ,如果你可以绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1 。如果存在解,则 保证 它是 唯一 的。

解题思路

解题过程:

思路一:贪心

  1. 统计总汽油量 totalSum,如果它小于0,必不可能走一圈,返回 -1
  2. 计算每一段路程的耗油量 curSum,找到 不小于 0 的下标,返回

思路二:贪心

  1. 统计总汽油量 sum,如果它小于0,必不可能走一圈,返回 -1
  2. 计算每天剩下的油 min,从 0 开始 判断,如果一圈下来 min 没有出现负数,则说明可以从 下标0 出发
  3. 如果 min 出现 负数,则说明不能从当前下标出发,从后往前 计算第一个不为负数的节点,即为起始点(说明从这个点出发,油会有剩余,可以补齐后面所需)

详细代码

思路一:贪心

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public int canCompleteCircuit(int[] gas, int[] cost) {
int totalSum = 0;
int curSum = 0;
int index = 0;
for(int i = 0; i < gas.length; i++){
curSum += (gas[i] - cost[i]);
totalSum += (gas[i] - cost[i]);
if(curSum < 0){
index = (i + 1) % gas.length;
curSum = 0;
}
}
if(totalSum < 0){
return -1;
}
return index;
}
}

思路二:贪心

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Solution {
public int canCompleteCircuit(int[] gas, int[] cost) {
int sum = 0;
int min = 0;

for(int i = 0; i < gas.length; i++){
sum += gas[i] - cost[i];
min = Math.min(sum, min);
}

if(sum < 0){
return -1;
}
if(min >= 0){
return 0;
}
for(int i = gas.length - 1; i >= 0; i--){
min += (gas[i] - cost[i]);
if(min >= 0){
return i;
}
}
return -1;
}
}

135. 分发糖果

题目链接:https://leetcode.cn/problems/candy/description/

n 个孩子站成一排。给你一个整数数组 ratings 表示每个孩子的评分。你需要按照以下要求,给这些孩子分发糖果:

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

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

解题思路

解题过程:

  1. 局部最优:

    • 从左往右:右边比左边大,则右边比左边多一个
    • 从右往左:左边比右边大,则判断当前 左边糖果数量 是否比右边糖果数量多,多就不变,少就变成 右边糖果数量 + 1
  2. 全局最优:累加,返回

详细代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public int candy(int[] ratings) {
int len = ratings.length;
int[] candyVec = new int[len];
candyVec[0] = 1;

for(int i = 1; i < len; i++){
candyVec[i] = (ratings[i] > ratings[i - 1]) ? candyVec[i - 1] + 1 : 1;
}

for(int i = len - 2; i >= 0; i--){
if(ratings[i] > ratings[i + 1]){
candyVec[i] = Math.max(candyVec[i], candyVec[i + 1] + 1);
}
}

int ans = 0;
for(int num : candyVec){
ans += num;
}
return ans;
}
}