#诗经·秦风·驷驖
驷驖(tie3)孔阜(fu4),六辔(pei4)在手。公之媚子,从公于狩。
奉时辰牡,辰牡孔硕。公曰左之,舍拔则获。
游于北园,四马既闲。輶(you2)车鸾镳(biao3),载猃(xian3)歇骄。
0.贪心算法概述
\qquad 从问题的初始状态出发,设定某种规律,不断进行贪心选择取得当前最优,最终得到整个问题的(一个)最优解。
1.分糖果
Q:
A:设定贪心规律:
- s[i]不能满足某个g[j],则不能满足g[j+1]及后续;
- g[j]如果能用s[i]满足,则不需要用s[i+1]及后续更大来满足,因此可以保留更大的来满足后续的g[j+1];【贪心】
- 对g[j]从小到大满足,优先配给小的g[j].【贪心】
#include<algorithm>
int findContent(vector<int>& g, vector<int>& s)
{
sort(g.begin(),g.end());
sort(s.begin(),s.end());
int g_index = 0;
int s_index = 0;
while(g_index<g.size()&&s_index<s.size())
{
if(g[g_index]<=s[s_index]) ++g_index;
++s_index;
}
return g_index;
}
2.摆动序列
A: 利用有限装态转移的逻辑
int wiggleMaxLength(vector<int>& nums)
{
if(nums.size()<2) return nums.size();
static const int BEGIN = 0;
static const int UP = 1;
static const int DOWN = 2;
int max_length = 1;
int STATE = BEGIN;
for(int i=1; i<nums.size(); ++i)
{
switch(STATE)
{
case BEGIN:
if(nums[i]>nums[i-1])
{
STATE = UP;
++max_length;
}
else if(nums[i]<nums[i-1])
{
STATE = DOWN;
++max_length;
}
//在==状态不做任何转移
break;
}
case UP:
{
if(nums[i]<nums[i-1])
{
STATE = DOWN;
++max_length;
}
break;
}
case DOWN:
{
if(nums[i]>nums[i-1])
{
STATE = UP;
++max_length;
}
break;
}
}
return max_length;
}
3.移除k个数字
A:通过举例来发掘贪心规律:
从高位向低位遍历,如果对应的数字大于下一位数字,则将该位数去掉,得到的数字最小!
解决问题:
- 字符串遍历完,k>0,如何处理:s=“12345”,k=3;
- 数字中有"0"出现,如何处理,s=“100200”,k=1;
- 如何存储字符串并返回。
#include<vector>
#include<string>
string removeKdigits(string num, int k)
{
vector<int> vec_stack; //利用数组模拟栈,好遍历
string result = "";
for(int i=0; i<num.size(); ++i)
{
int number = num[i] - '0';
while(vec_stack.size() && k && vec_stack[vec_stack.size()]>number)
{
vec_stack.pop_back();
--k;
}
if(vec_stack.size() || number)
{
vec_stack.push_back(number);
}
}
while(vec_stack.size() && k>0)
{
vec_stack.pop_back();
--k;
}
for(int i=0; i<vec_stack.size(); ++i)
{
result.append(1,'0'+vec_stack[i]);
}
if(result=="") return "0";
return result;
}
4.跳跃游戏
A: 贪心规律:
- 列举出第i个位置能够到达的最远位置max_arrive[i] = i + nums[i], 第i位可以到达i+1,i+2,…,i+nums[i];
- 从第i位跳至max_arrive[j]中最大的位置;
- 从i出发,能够达到的位置配给最理想。
bool canJump(vector<int>& nums)
{
vector<int> max_arrive;
for(int i=0; i<nums.size(); ++i)
{//构建能够达到最远的数组max_arrive
max_arrive.push_back(i+nums[i]);
}
int at = 0; //遍历max_arrive数组
int max_index; //记录历史最远位置
while(at<nums.size() && at<=max_index)
{
if(max_index<max_arrive[at])
max_index = max_arrive[at];
++at;
}
if(at==nums.size()) return true;
return false;
}
//优化代码
bool canJump(vector<int>& nums)
{
int max_arrive = 0; //记录最远能到达的位置
for(int i=0; i<nums.size(); ++i)
{
if(i>k) return false;
max_arrive = max(k,nums[i]+i);
}
return true;
}
A贪心规律:在到达某点前一直不跳跃,发现从该点不能跳到更远的地方了,在之前肯定要跳跃;
int jump(vector<int>& nums)
{
if(nums.size()<=1) return 0;
int current_max_index = nums[0]; //可达到最远
int pre_max_index = nums[0];//最远前置节点的最远
int jump = 1;
for(int i=1; i<nums.size(); ++i)
{
if(current_max_index<i)
{
++jump;
current_max_index = pre_max_index;
//必须跳跃,并移至前置节点最远位置处
}
pre_max_index = max(pre_max_index,nums[i]+i);
}
return jump;
}
5.射击气球
A-贪心规律:
- 对各个气球进行排序,按左端点从小到大排序;
- 维护一个射击区间[start,end],能够击穿更多的气球;
- 当前区间无法击穿下一个气球,重新维护新的射击区间;
#include<algorithm>
#include<vector>
bool cmp(const vector<int>& a,const <vector>&b)
{
return a[0]<b[0];//对二维数组的头进行排序
}
int findMinArrowShots(vector<vector<int>>& points)
{
if(points.empty()) return 0;
sort(points.begin(), points.end(), cmp);
int arrow_num = 1;
int arrow_start = points[0][0];
int arrow_end = points[0][1];
for(int i=1; i<points.size(); ++i)
{
if(arrow_end>=points[i][0])
{//在能够贪心的情况下射击更多的气球
arrow_start = points[i][0];
if(arrow_end>points[i][1])
{
arrow_end = points[i][1];
}
}
else
{//不能够射击新的气球,则重新开启贪心区间
++arrow_num;
arrow_start = points[i][0];
arrow_end = points[i][1];
}
}
return arrow_num;
}
6.最优加油问题
A-贪心规律:
- 最大堆,来存储经过的加油站的汽油量;
- 从起点到终点遍历各个加油站之间的距离d;
- 汽油不够走d时,从最大堆中取出一个加油站,直至能够走距离d;
- 堆中全部读出不够d,则返回-1;
- 走完汽油减少d;
- 堆中添加新的加油站油量。
#include<vector>
#include<algorithm>
#include<queue>
bool cmp(const vector<int>& a, const vector<int>& b)
{
return a[0]>b[0];
}
int get_min_stop(int L, int P, vector<vector<int>>&stop)
{ //L表示起始距离,P表示起始油量
//stop表示到终点距离和加油站油量[距终点距离,剩余油量]
priority_queue<int> Q; //存储油量的最大堆
int result = 0;
stop.push_back(vector<int>(0,0)); //终点油量
for(int i=0; i<stop.size(); ++i)
{
int dis = L - stop[i][0]; //dis表示达到下一个加油站的距离
if(Q.empty()&&P<dis) return -1;
while(!Q.empty() && P<dis)
{
P += Q.top(); //贪心加油
Q.pop();
++result;
}
P = P-dis; //油够,路过加油站
L = stop[i][0]; //距离更新为加油站到终点距离
Q.push(stop[i][1]);//用最大堆记录油量,以备贪心加油
}
return result;
}
7. TODO
- 以期coding总是从头开始构思,应该以一般形式构建递推项,才能使得coding顺畅;
- 贪心思想还未完全掌握,特别是局部最优和全局最优,与动态规划要区别开。