Day34_贪心算法
9. K 次取反后最大化的数组和
对数组降序排列,遍历数组(从大到小),遇到负数优先将绝对值较大的负数转化为正数,这是局部最优的。如果 k 不够将所有负数都变为正数,则将绝对值较大的部分转为正数即可;如果将所有负数都转变为正数时,k 还有剩余,不断反转最小的数即可,最终若 k 剩余奇数,则最小的数为负,偶数为正。
class Solution {
public:
static bool cmp(const int a, const int b) {
return abs(a) > abs(b);
}
int largestSumAfterKNegations(vector<int>& nums, int k) {
int nums_size = nums.size();
int sum = 0;
sort(nums.begin(), nums.end(), cmp);
for (int i = 0; i < nums_size; ++i) {
if (nums[i] < 0 && k > 0) {
k--;
nums[i] = -nums[i];
}
sum += nums[i];
}
if (k & 1) sum -= 2 * nums[nums_size - 1]; // 如果还需要反转奇数次
return sum;
}
};
10. 加油站
134. 加油站
思路一:
暴力解法:
模拟整个过程,最坏情况每个点都走到最后失败,复杂度 O ( n 2 ) O(n^2) O(n2)
有一个样例过不了
class Solution {
public:
int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
int gas_size = cost.size();
for (int i = 0; i < gas_size; ++i) { // 枚举起点位置
int rest_gas = gas[i] - cost[i];
int cur_index = (i + 1) % gas_size; // 可能下一个位置已经回到数组开头了
while (rest_gas > 0 && cur_index != i) { // 必须是大于,如果等于0,无法下一次起步
rest_gas += gas[cur_index] - cost[cur_index];
cur_index = (cur_index + 1) % gas_size;
}
if (rest_gas >= 0 && cur_index == i) return i; // 最后一次的可把油用完
}
return -1;
}
};
思路二:
直接参考卡哥写法
直接从全局进行贪心选择,情况如下:
情况一:如果gas的总和小于cost总和,那么无论从哪里出发,一定是跑不了一圈的
情况二:rest[i] = gas[i]-cost[i]为一天剩下的油,i从0开始计算累加到最后一站,如果累加没有出现负数,说明从0出发,油就没有断过,那么0就是起点。
情况三:如果累加的最小值是负数,汽车就要从非0节点出发,从后向前,看哪个节点能这个负数填平,能把这个负数填平的节点就是出发节点。
class Solution {
public:
int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
int gas_size = cost.size();
int cur_rest = 0;
int min_rest = 0;
for (int i = 0; i < gas_size; ++i) {
cur_rest += gas[i] - cost[i];
if (cur_rest < min_rest) min_rest = cur_rest;
}
if (cur_rest < 0) return -1;
if (min_rest >= 0) return 0;
for (int i = gas_size - 1; i >= 0; i--) { // 枚举起点位置
min_rest += gas[i] - cost[i];
if (min_rest >= 0) return i;
}
return -1;
}
};
思路三:
- 总油量小于总消耗量的话,一定不能到达。
- 如果总油量不小于总消耗量的话,一定能到达。从头开始遍历所有的加油站,如果出现剩余油量为负数的情况,一定在后面存在剩余油量过剩的情况,可以在循环一圈回来的时候补全前面的亏空。从某个点出发一直到到达最后一个点都一直处于油有剩余的情况,则该点就是出发点。
class Solution {
public:
int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
int gas_size = cost.size();
int cur_rest = 0;
int tot_rest = 0;
int start_pos = 0;
for (int i = 0; i < gas_size; ++i) {
cur_rest += gas[i] - cost[i];
tot_rest += gas[i] - cost[i];
if (cur_rest < 0) {
cur_rest = 0;
start_pos = i + 1;
}
}
if (tot_rest < 0) return -1;
return start_pos;
}
};
12. 分发糖果
一开始想着两边都考虑,想不明白了,看了题解直呼妙不可言
分别从左到右,和从右到左考虑两次要简单很多。
从左到右考虑如果右边的评分高,给右边比左边多一个糖
从右到左,如果左边比右边评分高,给左边比右边多一个糖,但是还要考虑到左边的孩子要比他左边的孩子多一个糖(这在前一步考虑过了),所以综合考虑,左边孩子的糖数应该是其当前糖的数量与比右边孩子多一个糖的数量之中的最大值。
class Solution {
public:
int candy(vector<int>& ratings) {
int child_size = ratings.size();
vector<int> candy_list (child_size, 1); // 开始每个孩子至少有一个糖
int result = 0;
for (int i = 1; i < child_size; ++i) { // 从左向右
if (ratings[i] > ratings[i - 1]) candy_list[i] = candy_list[i - 1] + 1;
}
for (int i = child_size - 1; i > 0; --i) {
if (ratings[i - 1] > ratings[i]) candy_list[i - 1] = max(candy_list[i - 1], candy_list[i] + 1);
}
for (int i = 0; i < child_size; ++i) result += candy_list[i];
return result;
}
};