贪心算法学习
文章目录
预备知识:贪心法
遵循某种规律,不断贪心地选取当前最优策略的算法设计方法
局部最优等价于全局最优
1. 分糖果(easy)
题目说明
已知一些孩子和一些糖果,每个孩子需求为g,每个糖果大小为s,当s>g时,糖果可以满足孩子;求使用一定数量糖果,最多可以满足多少孩子?(孩子与糖果一一对应)
class Solution {
public:
int findContentChildren(std::vector<int>& g, std::vector<int>& s){
std::sort(g.begin(), g.end());
std::sort(s.begin(), s.end());
int child = 0;
int cookie = 0;
while (cookie < s.size() && child < g.size()){
if (g[child] <= s[cookie]){ //判断当前糖果是否满足当前孩子
child ++;
}
cookie ++;
}
return child;
}
};
2. 摇摆序列(medium)
题目说明
一个整数序列,如果两个相邻元素的差恰好正负(负正)交替出现,则该序列被称为摇摆序列,元素小于2直接为摇摆序列
给定一个随机序列,求该序列满足摇摆定义的最长子序列的长度。(子序列而非连续子序列)
思路:
状态机:开始、上升、下降三种状态;当处于上升状态时,当前动作有两种:输入大于上一位 则状态不转移;输入小于上一位 则子序列长度+1 且状态切换;下降同理。
需要注意相等的情况,这里直接跳过
class Solution {
public:
int wiggleMaxLength(std::vector<int>& nums){
if (nums.size() < 3) return nums.size();
static const int BEGIN = 0;
static const int UP = 1;
static const int DOWN = 2;
int STATE = BEGIN;
int max_length = 1;
for (int i = 1; i < nums.size(); i++){
switch(STATE){
case BEGIN:
if (nums[i-1] < nums[i]){
STATE = UP;
max_lenth ++;
}
else if(nums[i-1] > nums[i]){
STATE = DOWN;
max_lenth ++;
}
break;
case UP:
if (nums[i-1] > nums[i]){
STATE = DOWN;
max_lenth ++;
}
break;
case DOWN:
if (nums[i-1] < nums[i]){
STATE = UP;
max_lenth ++;
}
break;
}
}
return max_length;
}
};
3. 移除K个数字(medium)
题目说明
已知一个使用字符串表示的非负整数num,将num中的k个数字移除,求移除k个数字后,可以获得的最小的新数字(num不以0开头)
例:
input: num = “1432219” k = 3 ==> output = 1219
思考:
如果k=1,如何处理;
k=n 是否可以基于 k=n-1,即局部最优是否等于全局最优;
什么时候应该删除数字
class Solution {
public:
string removeKdigits(string num, int k){
vector<char> stk;
string result = "";
for (int i = 0; i < num.size(); i++){
int number = num[i];
while (stk.size() != 0 && number < stk[stk.size()-1] && k > 0){
stk.pop_back();
k --;
}
if (stk.size() != 0 || number != '0') //为了避免 "10200" 变为 "0200"
stk.push_back(number);
}
while(stk.size() != 0 && k > 0){
stk.pop_back();
k --;
}
for (int i = 0; i < stk.size(); i++){
result += stk[i];
}
if (result == "") return "0";
return result;
}
};
4. 跳跃游戏(medium)
题目说明
已知一非负整型数组,数组中nums[i]代表从数组第i位最多向前跳跃步数,求是否可以从0跳到最后一位
代码思路解读:
第一步:计算每个节点可到达的最远节点位置
第二步:判断节点间是否相通,节点位置在max_index覆盖范围内,说明当前节点是可抵达的。
同时过程中会不断更新max_index的值,扩大可抵达范围。
class Solution {
public:
int canJump(vector<int>& nums){
vector<int> index;
for (int i = 0; i < nums.size(); i++){
index.push(i + nums[i]);
}
int jump = 0;
int max_index = index[0];
while (jump < nums.size() && jump <= max_index){
if (max_index < index[jump]){
max_index = index[jump];
}
jump ++;
}
if (jump == nums.size()) return true;
return false;
}
};
5. 跳跃游戏2(hard)
题目说明
条件同上。确认可以跳到最后一个位置,最少需要几次跳跃?
代码思路解读:
我坚信目前的选择是最好的,但是我不放心,我会记录并更新我遍历过的各节点最远可抵达的位置
当目前遍历节点不在目前选择范围内时,我发觉,目前的选择是无法抵达终点的;还好,留有后手,我用记录的某节点最远可抵达位置替换目前的选择
class Solution {
public:
int jump(vector<int>& nums){
if (nums.size() < 2){
return 0;
}
int current_max_index = nums[0];
int pre_max_index = nums[0];
int jump_min = 1;
for (int i = 1; i < nums.size(); i++){
if (i > cur_max_index){
jump_min ++;
cur_max_index = pre_max_index;
}
if (pre_max_index < nums[i] + i){
pre_max_index = nums[i] + i
}
}
return jump_min;
}
};
6. 射击气球(medium)
题目说明
已知一个平台上有一定数量的气球,平面可以看作一个坐标系,在平面的x轴的不同位置安排弓箭手向y轴方向射箭,给定气球的宽度xstart <= x <= xend,问至少需要多少弓箭手,能将全部气球打爆
|
|
| _________|_
| | |
| —|———————— |
| | _|__
| ————|—— |
| | |
|———————|————————————————|————————— 弓箭手射出的箭:|
最少需要两个弓箭手
思考
一个弓箭手最多可以射破几个气球
代码思路解读
判断新进来的气球是否与目前弓箭手预选范围有交集:有,则一并毁灭;没有,则增加火力
class Solution {
public:
bool cmp(const pair<int, int>& a, const pair<int, int>& b){
return a.first < b.first;
}
int findMinArrowShots(vector<pair<int, int>>& points){
if (points.size() == 0) return 0;
sort(points.begin(), points.end(), cmp)
int shoot_num = 1;
int shoot_begin = points[0].first;
int shoot_end = points[0].second;
for (int i = 1; i < points.size(); i++){
if (points[i].first <= shoot_end){
shoot_begin = points[i].first;
if (shoot_end > points[i].second){
shoot_end = points[i].second
}
}
else{
shoot_num ++;
shoot_begin = points[i].first;
shoot_end = points[i].second;
}
}
return shoot_num;
}
};
7. 最优加油方法(hard)
题目说明
已知一条公路上有一个起点和一个终点,之间有n个加油站,并且知道n个加油站到终点的距离d与各个加油站可以加油的量l,起点至终点距离L与起始时邮箱中汽油量P;
假设使用1单位汽油即走1个单位的距离,邮箱没有上限,最少可以加几次油,可以从起点走到终点?(无法到达返回-1)
思考:
何时加油最合适 油用光的时候
在哪个加油站加油最合适 油最多的地方
代码思路讲解:
(本题极度类似跳跃游戏2,只是多引入了一个变量)
最大堆的意义:有可能当前地点到下一个加油站距离超远,需要前边部分或所有的加油站都加油才能抵达,而为了减少加油次数,我们选择过程中油量最多的加油站加油,故需要用到最大堆
第一步:判断当前油量是否可以抵达下一个加油站,否:反悔一下,我想加油了,一次不够,那就多反悔几次
不好意思,没有第二步
class Solution {
public:
bool cmp(const pair<int, int>& a, const pair<int, int>& b){
return a.first > b.first;
}
int get_minimum_stop(int L, int P, vector<pair<int, int>>& stop){
priority_queue<int> Q;
int result = 0;
stop.push_back(make_pair(0, 0));
sort(stop.begin(), stop_end(), cmp);//按照加油站至终点的距离排序
for (int i = 0; i < stop.size(); i++){
int dis = L - stop[i].first;
while (!Q.empty() && P < dis){
P += Q.top();
Q.pop();
result ++;
}
if (Q.empty() && P < dis) return -1;
P -= dis;
L = stop[i].first;
Q.push(stop[i].second);
}
return result;
}
};
下期预告:递归、回溯与分治