第八章 贪心算法 part03
1.LeetCode. K次取反后最大化的数组和
1.1题目链接:1005.K次取反后最大化的数组和
1.2思路:贪心的思路,局部最优:让绝对值大的负数变为正数,当前数值达到最大,整体最优:整个数组和达到最大。
局部最优可以推出全局最优。
那么如果将负数都转变为正数了,K依然大于0,此时的问题是一个有序正整数序列,如何转变K次正负,让 数组和 达到最大。
那么又是一个贪心:局部最优:只找数值最小的正整数进行反转,当前数值和可以达到最大(例如正整数数组{5, 3, 1},反转1 得到-1 比 反转5得到的-5 大多了),全局最优:整个 数组和 达到最大。
那么本题的解题步骤为:
第一步:将数组按照绝对值大小从大到小排序,注意要按照绝对值的大小
第二步:从前向后遍历,遇到负数将其变为正数,同时K–
第三步:如果K还大于0,那么反复转变数值最小的元素,将K用完
第四步:求和
1.3附加代码如下所示:
class Solution {
public:
static bool Mycompare(const int &a,const int &b)
{
return abs(a)>abs(b);
}
int largestSumAfterKNegations(vector<int>& nums, int k) {
sort(nums.begin(),nums.end(),Mycompare);
for(int i=0;i<nums.size();i++)
{
if(nums[i]<0&&k>0)
{
nums[i]*=-1;
k--;
}
}
if(k%2==1)nums[nums.size()-1]*=-1;
//if(k%2==0)nums[nums.size()-1]*=1;//可不写
int result=0;
for(int i=0;i<nums.size();i++)
{
result+=nums[i];
}
return result;
}
};
2.LeetCode.加油站
2.1题目链接:134. 加油站
2.2思路:可以换一个思路,首先如果总油量减去总消耗大于等于零那么一定可以跑完一圈,说明 各个站点的加油站 剩油量rest[i]相加一定是大于等于零的。每个加油站的剩余量rest[i]为gas[i] - cost[i]。i从0开始累加rest[i],和记为curSum,一旦curSum小于零,说明[0, i]区间都不能作为起始位置,因为这个区间选择任何一个位置作为起点,到i这里都会断油,那么起始位置从i+1算起,再从0计算curSum。
2.3附加代码如下所示:
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;
}
};
3.LeetCode.两个数组的交集
3.1题目链接:135. 分发糖果
3.2思路:这道题目一定是要确定一边之后,再确定另一边,例如比较每一个孩子的左边,然后再比较右边,如果两边一起考虑一定会顾此失彼。
先确定右边评分大于左边的情况(也就是从前向后遍历)
此时局部最优:只要右边评分比左边大,右边的孩子就多一个糖果,全局最优:相邻的孩子中,评分高的右孩子获得比左边孩子更多的糖果
局部最优可以推出全局最优。
再确定左孩子大于右孩子的情况(从后向前遍历)
遍历顺序这里有同学可能会有疑问,为什么不能从前向后遍历呢?
因为 rating[5]与rating[4]的比较 要利用上 rating[5]与rating[6]的比较结果,所以 要从后向前遍历。
如果从前向后遍历,rating[5]与rating[4]的比较 就不能用上 rating[5]与rating[6]的比较结果了 。如图:
所以确定左孩子大于右孩子的情况一定要从后向前遍历!
如果 ratings[i] > ratings[i + 1],此时candyVec[i](第i个小孩的糖果数量)就有两个选择了,一个是candyVec[i + 1] + 1(从右边这个加1得到的糖果数量),一个是candyVec[i](之前比较右孩子大于左孩子得到的糖果数量)。
那么又要贪心了,局部最优:取candyVec[i + 1] + 1 和 candyVec[i] 最大的糖果数量,保证第i个小孩的糖果数量既大于左边的也大于右边的。全局最优:相邻的孩子中,评分高的孩子获得更多的糖果。
局部最优可以推出全局最优。
所以就取candyVec[i + 1] + 1 和 candyVec[i] 最大的糖果数量,candyVec[i]只有取最大的才能既保持对左边candyVec[i - 1]的糖果多,也比右边candyVec[i + 1]的糖果多。
如图:
3.3附加代码如下所示:
class Solution {
public:
int candy(vector<int>& ratings) {
vector<int>Candy(ratings.size(),1);//因为每个孩子至少有一个糖果,所以初始化都为1
// 从前向后
for(int i=1;i<ratings.size();i++)//从左往右遍历,比较右边是否大于左边
{
if(ratings[i]>ratings[i-1])
{
Candy[i]=Candy[i-1]+1;
}
}
// 从后向前
for(int i=ratings.size()-2;i>=0;i--)
{
if(ratings[i]>ratings[i+1])
{
Candy[i]=max(Candy[i+1]+1,Candy[i]);
}
}
int result=0;
for(int i=0;i<ratings.size();i++)
{
result+=Candy[i];
}
return result;
}
};