给定一个整数数组 A,我们只能用以下方法修改该数组:我们选择某个索引 i 并将 A[i] 替换为 -A[i],然后总共重复这个过程 K 次。(我们可以多次选择同一个索引 i。)
以这种方式修改数组后,返回数组可能的最大和。
视频讲解https://www.bilibili.com/video/BV138411G7LY/?spm_id_from=333.788&vd_source=f98f2942b3c4cafea8907a325fc56a48文章讲解
https://programmercarl.com/1005.K%E6%AC%A1%E5%8F%96%E5%8F%8D%E5%90%8E%E6%9C%80%E5%A4%A7%E5%8C%96%E7%9A%84%E6%95%B0%E7%BB%84%E5%92%8C.html
- 思路:两次贪心
- 绝对值大的负数变为正数(局部最优)👉整个数组和达到最大(全局最优);
- 如果将负数都转变为正数了,K依然大于0,局部最优:只找数值最小的正整数进行反转,当前数值和可以达到最大👉全局最优:整个数组和达到最大。
- 具体步骤:
- 第一步:将数组按照绝对值大小从大到小排序,注意要按照绝对值的大小
- 第二步:从前向后遍历,遇到负数将其变为正数,同时K--
- 第三步:如果K还大于0,那么反复转变数值最小的元素,将K用完
- 第四步:求和
- 代码:
class Solution {
static bool cmp(int a, int b) {
return abs(a) > abs(b);
}
public:
int largestSumAfterKNegations(vector<int>& A, int K) {
sort(A.begin(), A.end(), cmp); // 第一步
for (int i = 0; i < A.size(); i++) { // 第二步
if (A[i] < 0 && K > 0) {
A[i] *= -1;
K--;
}
}
if (K % 2 == 1) A[A.size() - 1] *= -1; // 第三步
int result = 0;
for (int a : A) result += a; // 第四步
return result;
}
};
在一条环路上有 N 个加油站,其中第 i 个加油站有汽油 gas[i] 升。你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。
如果你可以绕环路行驶一周,则返回 出发时加油站的编号,否则返回 -1。
- 如果题目有解,该答案即为唯一答案。
- 输入数组均为非空数组,且 gas 与 cost 长度相同。
- 输入数组中的元素均为非负数。
文章讲解https://programmercarl.com/0134.%E5%8A%A0%E6%B2%B9%E7%AB%99.html
- 思路:
- 暴力解法:
- 遍历每一个加油站为起点的情况,模拟一圈。
- 如果跑了一圈,中途没有断油,而且最后油量大于等于0,说明这个起点是ok的。
- for循环适合模拟从头到尾的遍历,而while循环适合模拟环形遍历!
- 贪心算法:
- 方法一:
情况一:
如果 gas的总和 小于 cost总和,那么无论从哪里出发,都跑不了一圈;
情况二:
rest[i] = gas[i] - cost[i] 为一天剩下的油,i从0开始计算累加到最后一站,如果累加没有出现负数,说明从0出发,油就没有断过,那么0就是起点。
情况三:
如果累加的最小值是负数,汽车就要从非0节点出发,从后向前,看哪个节点能将这个负数填平,即为出发节点。
- ⭐方法二:
每个加油站的剩余量rest[i]为gas[i] - cost[i]。
i从0开始累加rest[i],和记为curSum,一旦curSum小于零,说明[0, i]区间都不能作为起始位置,起始位置从i+1算起,再从0计算curSum。
- 方法一:
- 暴力解法:
- 代码:
// 暴力解法
class Solution {
public:
int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
for (int i = 0; i < cost.size(); i++) {
int rest = gas[i] - cost[i]; // 记录剩余油量
int index = (i + 1) % cost.size();
while (rest > 0 && index != i) { // 模拟以i为起点行驶一圈
rest += gas[index] - cost[index];
index = (index + 1) % cost.size();
}
// 如果以i为起点跑一圈,剩余油量>=0,返回该起始位置
if (rest >= 0 && index == i) return i;
}
return -1;
}
};
// 时间复杂度:O(n^2)
// 空间复杂度:O(1)
// 贪心算法:方法一
class Solution {
public:
int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
int curSum = 0;
int min = INT_MAX; // 从起点出发,油箱里的油量最小值
for (int i = 0; i < gas.size(); i++) {
int rest = gas[i] - cost[i];
curSum += rest;
if (curSum < min) {
min = curSum;
}
}
if (curSum < 0) return -1; // 情况1
if (min >= 0) return 0; // 情况2
// 情况3
for (int i = gas.size() - 1; i >= 0; i--) {
int rest = gas[i] - cost[i];
min += rest;
if (min >= 0) {
return i;
}
}
return -1;
}
};
// 时间复杂度:O(n)
// 空间复杂度:O(1)
// 贪心算法:方法二
class Solution {
public:
int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
int curSum = 0;
int totalSum = 0;
int start = 0;
for (int i = 0; i < gas.size(); i++) {
curSum += gas[i] - cost[i];
totalSum += gas[i] - cost[i];
if (curSum < 0) { // 当前累加rest[i]和 curSum一旦小于0
start = i + 1; // 起始位置更新为i+1
curSum = 0; // curSum从0开始
}
}
if (totalSum < 0) return -1; // 说明怎么走都不可能跑一圈了
return start;
}
};
// 时间复杂度:O(n)
// 空间复杂度:O(1)
老师想给孩子们分发糖果,有 N 个孩子站成了一条直线,老师会根据每个孩子的表现,预先给他们评分。你需要按照以下要求,帮助老师给这些孩子分发糖果:
- 每个孩子至少分配到 1 个糖果。
- 相邻的孩子中,评分高的孩子必须获得更多的糖果。
老师至少需要准备多少颗糖果呢?
- 输入: [1,2,2]
- 输出: 4
- 解释: 你可以分别给这三个孩子分发 1、2、1 颗糖果,第三个孩子只得到 1 颗糖果。
视频讲解https://www.bilibili.com/video/BV1ev4y1r7wN/?spm_id_from=333.788&vd_source=f98f2942b3c4cafea8907a325fc56a48文章讲解
https://programmercarl.com/0135.%E5%88%86%E5%8F%91%E7%B3%96%E6%9E%9C.html
- 思路:确定一边之后,再确定另一边(先比较每一个孩子 i 的左边 i - 1,再比较右边 i + 1)
- 从前向后遍历
// 从前向后 for (int i = 1; i < ratings.size(); i++) { if (ratings[i] > ratings[i - 1]) candyVec[i] = candyVec[i - 1] + 1; }
- 局部最优:只要右边评分比左边大,右边的孩子就多一个糖果
- 全局最优:相邻的孩子中,评分高的右边孩子获得比左边孩子更多的糖果
- 从后向前遍历
// 从后向前 for (int i = ratings.size() - 2; i >= 0; i--) { if (ratings[i] > ratings[i + 1] ) { candyVec[i] = max(candyVec[i], candyVec[i + 1] + 1); } }
如果 ratings[i] > ratings[i + 1],此时candyVec[i](第i个小孩的糖果数量)就有两个选择了,一个是candyVec[i + 1] + 1(从右边这个加1得到的糖果数量),一个是candyVec[i](之前比较右孩子大于左孩子得到的糖果数量)。candyVec[i]只有取最大的才能既保持对左边candyVec[i - 1]的糖果多,又比右边candyVec[i + 1]的糖果多。
- 从前向后遍历
- 代码:
class Solution {
public:
int candy(vector<int>& ratings) {
vector<int> candyVec(ratings.size(), 1);
// 从前向后
for (int i = 1; i < ratings.size(); i++) {
if (ratings[i] > ratings[i - 1]) candyVec[i] = candyVec[i - 1] + 1;
}
// 从后向前
for (int i = ratings.size() - 2; i >= 0; i--) {
if (ratings[i] > ratings[i + 1] ) {
candyVec[i] = max(candyVec[i], candyVec[i + 1] + 1);
}
}
// 统计结果
int result = 0;
for (int i = 0; i < candyVec.size(); i++) result += candyVec[i];
return result;
}
};
⭐总结:如果在考虑局部的时候想两边兼顾,就会顾此失彼。
采用了两次贪心的策略:
- 一次是从左到右遍历,只比较右边孩子评分比左边大的情况。
- 一次是从右到左遍历,只比较左边孩子评分比右边大的情况。
这样从局部最优推出了全局最优,即:相邻的孩子中,评分高的孩子获得更多的糖果。