1 加油站
LeetCode:加油站
值得注意的是,由于每一次加油量和耗油量是固定的,这意味着邮箱剩余油量的变化曲线是固定的,只因为起点的不同而存着严格的上下平移关系。
因此从全局最优角度出发,剩余油量最低的站点作为起点,即可保证所有站点的剩余油量>=0(可行的情况下)。
另一种思想,从局部最优角度出发,从0起步,一旦到达站点i剩余油量<0,那么[0,i]的所有站点都不应该被作为起步点,因为这样的话到这里就一定没油了,更替为i+1作为起步点(这样可以保证区间的左端点剩余油量必定大于0)
全局最优
class Solution {
public:
int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
int sum=0;
int min_val=gas[0]-cost[0];
int index=0;
for(int i=0;i<gas.size();i++)
{
//sum为从0出发,到i+1处的油箱剩余量
//无论什么方式出发,每个站的加油量是固定的,因此油箱剩余量变化曲线形状固定
//剩余量最低的时候,代表着到达i+1处油量最紧张
//以此作为起点,就相当于剩余量为0,那其他地方的剩余量必然>=0
sum+=gas[i]-cost[i];
if(min_val>=sum)
{
index=(i+1)%gas.size();
min_val=sum;
}
}
if(sum<0)return -1;
return index;
}
};
局部最优(贪心)
class Solution {
public:
int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
int curSum=0;
int totalSum=0;
int index=0;
for(int i=0;i<gas.size();i++)
{
int rest=gas[i]-cost[i];
curSum+=rest;
totalSum+=rest;
if(curSum<0)
{
curSum=0;
index=i+1;
}
}
if(totalSum<0)return -1;
return index;
}
};
2 分发糖果
LeetCode:分发糖果
对于这种复杂的极值点求解,一定要用贪心将算法解耦,将复杂的两端判断转化为两次贪心的一段判断。
本题中,先从前到后遍历,将只看左边的最少糖果分发,再从后向前遍历,将只看右边的最少糖果与(左边最小糖果)比较,取最大值分配。
取最大值的原因在于,这是从一侧的要求看来必须要满足的最少糖果,而必须同时满足两侧的要求。
class Solution {
public:
int candy(vector<int>& ratings) {
vector<int> nums(ratings.size(),1);
//从1开始,只要比左边大,就多发一颗糖
for(int i=1;i<ratings.size();++i)
{
if(ratings[i]>ratings[i-1])
nums[i]=1+nums[i-1];
}
//从n-2开始,必要比右边大,就必须取已有值(从左边看的结果)和(从右看结果)中选最大值
for(int i=ratings.size()-2;i>=0;--i)
{
if(ratings[i]>ratings[i+1])
{
nums[i]=max(nums[i],1+nums[i+1]);
}
}
int count=0;
for(int x:nums)count+=x;
return count;
}
};
3 柠檬水找零
LeetCode:分发饼干
虽然知道是贪心,但感觉更像是常识题,甚至改一改零钱金额还能变成动态规划题。
class Solution {
public:
bool lemonadeChange(vector<int>& bills) {
int num5=0;
int num10=0;
for(int i=0;i<bills.size();i++)
{
//5美元就收入
if(bills[i]==5)
++num5;
//10美元
else if(bills[i]==10)
{
//10美元零钱+1
++num10;
//可以找零,5美元零钱减少
if(num5>=1)
--num5;
//不能找零,false
else
return false;
}
//20美元
else
{
//尽可能地用10美元+5美元找零
if(num10>=1 && num5>=1)
{
--num10;
--num5;
continue;
}
else if(num5>=3)
{
num5-=3;
continue;
}
else
{
return false;
}
}
}
return true;
}
};
4 根据身高重建队列
LeetCode:分发饼干
又是同时面对h和k的双重规范,应该以其中一者作为标准进行排序,之后再慢慢调整。
相对理想的是使用身高h作为排序标准,从高到低排列,h一致的k小的在前。
1 排序后,高的在前,意味着people=[h,k],前k个必然比h高,直接将people插入k即可。
2 从高到低的插入,因为k必然<=i(代表着有i个人比h高,如果i<k,则k的意义不成立)
3 所以people插入k的位置后,对于i+1的people’而言,前面数组依然都大于people’的h’只是内部发生了变化,依然保证前k’个必然比h’高,直接将people’插入k’即可
1 身高排序
class Solution {
static bool cmp(vector<int> a,vector<int> b)
{
//高的在前
if(a[0]>b[0])
return true;
//一样高的,k小的在前
else if(a[0]==b[0])
return a[1]<=b[1];
else
return false;
}
public:
vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
sort(people.begin(),people.end(),cmp);
vector<vector<int>> queue;
//排序后,高的在前,意味着people=[h,k],前k个必然比h高,直接将people插入k即可
//从高到低的插入,因为k必然<=i(代表着有i个人比h高,如果i<k,则k的意义不成立)
//所以people插入k的位置后,对于i+1的people'而言,前面数组依然都大于people'的h'
//只是内部发生了变化,依然保证前k'个必然比h'高,直接将people'插入k'即可
for(int i=0;i<people.size();++i)
{
//一般而言,i>=k是显然的
int position=people[i][1];
queue.insert(queue.begin()+position,people[i]);
}
return queue;
}
};
2 k排序(接近暴力法)
class Solution {
static bool cmp(vector<int> a,vector<int> b)
{
if(a[1]<b[1])
return true;
else if(a[1]==b[1])
return a[0]<=b[0];
else
return false;
}
public:
vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
sort(people.begin(),people.end(),cmp);
vector<vector<int>> queue;
queue.push_back(people[0]);
int count=1;
int larger=0;
while(count<people.size())
{
vector<int> elem=people[count++];
larger=0;
for(int i=0;i<queue.size();i++)
{
if(queue[i][0]>=elem[0])
{
++larger;
}
if(larger>elem[1])
{
queue.insert(queue.begin()+i,elem);
break;
}
else if(i==queue.size()-1 && larger==elem[1])
{
queue.insert(queue.begin()+i+1,elem);
break;
}
}
}
return queue;
}
};
5 用最少数量的箭引爆气球
LeetCode:用最少数量的箭引爆气球
对气球起始位置进行排序,并根据终点位置调整发射点,发射点始终为这一轮射击气球的最小终止位置。一旦起始位置超过发射点,就需要发射第二颗子弹。
class Solution {
static bool cmp(vector<int>& p1,vector<int>& p2)
{
return p1[0]<p2[0];
}
public:
int findMinArrowShots(vector<vector<int>>& points) {
//按照起始位置排序
sort(points.begin(),points.end(),cmp);
//每一次新发射的子弹,必然在上一次子弹没法射到的气球的右边界
//即在保证不漏气球的情况下,每一发子弹都尽可能靠右
//那么很显然,第一发子弹的初始预定发射点应该是points[0][1]
//并在过程中对发射点进行调整(向左调整)
int edge=points[0][1];
int shots=1;
for(int i=1; i<points.size(); ++i)
{
//这发子弹射不到该气球
if(points[i][0]>edge)
{
//更改发射点
edge=points[i][1];
//发射
++shots;
}
//试试调整
else
{
edge=min(points[i][1],edge);
}
}
return shots;
}
};
6 总结
贪心算法感觉毫无规律可言,完全就是靠着自己的思路去硬生生地解题,有点痛苦。
只能依靠多做题培养题感,令人忧伤啊。
——2023.2.27