【数据结构与算法-贪心算法经典例题汇总】

  • 典例1、分发糖果/分发饼干(easy)
  • 典例2、摇摆序列(medium)
  • 典例3、移除K个数字(medium)
  • 典例4、跳跃游戏-a(medium)
  • 典例5、跳跃游戏2(hard)
  • 典例6、用最少数量的箭射击气球(medium)
  • 典例7、最优加油策略(hard)


  • 基础搭建:
  • 从钞票找零与最佳的实际情况中理解贪心算法的思想:
  • 尽可能的使用较大的面值的钞票支付,需要的钱张数最少!
  • 贪心算法的设计思想:即是遵从某种规律不断的贪心的选择当前情况下最优策略的算法实现方法。
  • 示例:
// 使用最少的张数,凑够 556 元;可选面额:{100, 50, 20, 10, 5, 2, 1}
#include <stdio.h>

int main(){
	const int RMB[] = {100, 50, 20, 10, 5, 2, 1};
	const int NUM = 7; //  7种面值
	int X = 556; // 最少多少张凑够
	int count = 0;
	for (int i = 0; i < NUM; i++){
		int use = X / RMB[i]; // 需要面额为 RMB[i] 的 use 张
		count += use;  // 总数增加use张 
		X = X - RMB[i] * use;   // 钱数总额减去用RMB[i]组成的额度
		printf("需要面额为%d的%d张, ", RMB[i], use);
		printf("剩余需要支付RMB %d.\n", X);
	}
	printf("总共需要%d张RMB\n", count);
	return 0;
}

典例1、分发糖果/分发饼干(easy)

class Solution {
public:
    int findContentChildren(vector<int>& g, vector<int>& s) {
        std::sort(g.begin(),g.end()); // 排序
        std::sort(s.begin(),s.end());
        int child = 0;  // child 代表已经被满足的孩子的个数,
        int cookie  = 0; // cookie 代表尝试分发的饼干数
        while(cookie<s.size()&&child<g.size()){ // 当孩子数目和饼干数目均未尝试匹配完时
            if (g[child]<=s[cookie]){ // 孩子的需求小于等于饼干的大小时
                child++; // 当前孩子被满足,指向当前孩子的指针向后移动1
            }
            cookie++; // 无论成果与否,每个糖果均尝试一次,cookie+1
        }
        return child;  // 最终child的孩子数目就是满足的最多数目
    }
};
  • 可本地运行测试的完整代码:
#include <stdio.h>

#include <vector>
#include <algorithm>
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(child < g.size() && cookie < s.size()){
	    	if (g[child] <= s[cookie]){
	    		child++;
			}
			cookie++;
	    }
    	return child;
    }
};

int main(){
	Solution solve;
	std::vector<int> g;
	std::vector<int> s;
	g.push_back(5);
	g.push_back(10);
	g.push_back(2);
	g.push_back(9);
	g.push_back(15);
	g.push_back(9);
	s.push_back(6);
	s.push_back(1);
	s.push_back(20);
	s.push_back(3);
	s.push_back(8);	
	printf("%d\n", solve.findContentChildren(g, s));
	return 0;
}

典例2、摇摆序列(medium)

  • 方法:(贪心)
  • 题目描述:
  • 思路分析:
  • 归纳:

贪心算法 python 贪心算法经典例题_数据结构与算法_04

  • 使用状态机转换:(相比于单纯写 if - else ,架构上更加清晰)
  • 贪心算法 python 贪心算法经典例题_c++_05

  • 对于贪心算法,最好先找几个例子,把思路确定下来,再整理代码。
  • LeetCode提交OJ测试链接:
  • OJ测试代码实现:
class Solution {
public:
    int wiggleMaxLength(vector<int>& nums) {
        if (nums.size()<2){
            return nums.size();
        }
        static const int STATE_BEGIN = 0;  // 描述三种序列的状态
        static const int STATE_UP = 1;
        static const int STATE_DOWN = 2;
        int STATE = STATE_BEGIN;  // 状态转换的变量
        int max_result = 1; // 最大的长度最小为 1
        // 从第二个数字开始遍历判断
        for (int i=1;i<nums.size();i++ ){ 
            switch (STATE){     
            case(STATE_BEGIN):  // 开始状态
                if (nums[i-1]<nums[i]){    // 判断序列是否为上升状态
                    STATE = STATE_UP;
                    max_result++;  // 最大长度 +1
                }
                else if(nums[i-1]>nums[i]){ //  判断序列是否为下降状态
                    STATE = STATE_DOWN;
                    max_result++;
                }
                break;
            case(STATE_UP): // 上升状态
                if (nums[i-1]>nums[i]){   
                    STATE = STATE_DOWN; 
                    max_result++;
                }
                break;
            case(STATE_DOWN):  // 下降状态
                if (nums[i-1]<nums[i]){  
                    STATE = STATE_UP;
                    max_result++;
                }
                break;
            }
        }
        return max_result;
    }
};
  • 可本地运行测试的完整代码:
#include <stdio.h>

#include <vector>
class Solution {
public:
    int wiggleMaxLength(std::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 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_length++;
		    	}
		    	else if (nums[i-1] > nums[i]){
	    			STATE = DOWN;
	    			max_length++;
	    		}
	    		break;
	    	case UP:
	    		if (nums[i-1] > nums[i]){
	    			STATE = DOWN;
		    		max_length++;
		    	}
		    	break;
	    	case DOWN:
	    		if (nums[i-1] < nums[i]){
	    			STATE = UP;
		    		max_length++;
		    	}
		    	break;
		    }
	    }
    	return max_length;
    }
};

int main(){
	std::vector<int> nums;
	nums.push_back(1);
	nums.push_back(17);
	nums.push_back(5);
	nums.push_back(10);
	nums.push_back(13);
	nums.push_back(15);
	nums.push_back(10);
	nums.push_back(5);
	nums.push_back(16);
	nums.push_back(8);
	Solution solve;
	printf("%d\n", solve.wiggleMaxLength(nums));
	return 0;
}

典例3、移除K个数字(medium)

  • 方法:(贪心&栈)
  • 题目描述:
  • 贪心算法 python 贪心算法经典例题_数据结构与算法_06

  • 思考:
    假如对于1432219,只去掉一个数值(k=1)时,应该去掉什么数值?
    若 k > 1, 则应该按照什么样的顺序与策略进行逐渐的删除数字,得到最小的结果?
  • 思路:
  • 贪心算法 python 贪心算法经典例题_贪心算法 python_07

  • 栈演示:

贪心算法 python 贪心算法经典例题_#include_08

  • 补充:
    数字转字符:string s = s [ i ] + '0' ,其中 s 为整数类型
    字符转数字:int number = num[ i ] - '0' , 其中num为字符串类型
  • 特殊情况:
    当所有的数字都扫描完成,K仍然 大于 0,应当如何处理?(如 12345 , K=2)
    vector也可当做栈容器,vector 可遍历,也从尾部移除数据;vector.push_back()
    当数字中有0出现,该怎么处理?(如 100200,K = 1)
    最后的结果如何存储为字符串返回?
  • LeetCode提交OJ测试链接:
  • OJ测试代码实现:
class Solution {
public:
    string removeKdigits(string num, int k) {
        std::vector<int> s;// 使用vector当做栈容器,因为vector 可以遍历,可以从尾部删除元素
        std::string result = ""; // 存储最终的字符串类型的结果
        for(int i = 0;i<num.length();i++){ // 从最高位循环遍历数字num
            int number = num[i]-'0'; // 字符型数值转化为整数型数值
            while(s.size() != 0 && s[s.size()-1]>number && k>0 ){// 当栈不空,栈顶元素大于数 number ,仍然可以删除数字时,循环继续
                s.pop_back(); // 弹出栈顶元素
                k--;
            } 
            if (number != 0 || s.size() !=0){//     1111111111        125000
                s.push_back(number);// 将数字number压入栈中
            }
        }
        while(s.size() !=0 && k > 0){ // 如果栈不空,且仍然可以删除数字 (12345)
            s.pop_back(); //直接删除数字
            k--; //直接删除数字
        }
        for (int i = 0;i<s.size();i++){// 将栈中的元素从头遍历,存储到result
            result.append(1,'0'+s[i]);    // 将元素由整形数值转化为字符串数值
        }
        if (result == ""){
            result = "0" ;
        }
        return result;
    }
};
  • 可本地运行测试的完整代码:
#include <stdio.h>

#include <string>
#include <vector>

class Solution {
public:
    std::string removeKdigits(std::string num, int k) {
    	std::vector<int> S;
    	std::string result = "";
    	for (int i = 0; i < num.length(); i++){
	    	int number = num[i] - '0';
	    	while(S.size() != 0 && S[S.size()-1] > number && k > 0){
	    		S.pop_back();
	    		k--;
	    	}
	    	if (number != 0 || S.size() != 0){
	    		S.push_back(number);
	    	}
	    }
	    while(S.size() != 0 && k > 0){
    		S.pop_back();
    		k--;
    	}
	    for (int i = 0; i < S.size(); i++){
    		result.append(1, '0' + S[i]);
    	}
    	if (result == ""){
	    	result = "0";
	    }
    	return result;
    }
};

int main(){
	Solution solve;
	std::string result = solve.removeKdigits("1432219", 3);
	printf("%s\n", result.c_str());
	std::string result2 = solve.removeKdigits("100200", 1);
	printf("%s\n", result2.c_str());
	return 0;
}

典例4、跳跃游戏-a(medium)

  • 方法:(贪心)
  • 题目描述:
  • 贪心算法 python 贪心算法经典例题_#include_09

  • 分析:
  • 贪心算法 python 贪心算法经典例题_数据结构与算法_10

  • 思路:

贪心算法 python 贪心算法经典例题_数据结构与算法_11

class Solution {
public:
    bool canJump(vector<int>& nums) {
        std::vector<int> index; // 声明一个vector,存放每个位置可跳跃到的最大位置
        for(int i=0;i<nums.size();i++){
            index.push_back(i+nums[i]);// 计算当前位置可跳跃的最大位置
        }
        int start_jump = 0; //初始化起跳的位置
        int max_index = index[0]; //当前起跳位置处可到达的最大位置
        while(start_jump<index.size() && start_jump<= max_index){//直到start_jump跳到超过数组尾部,或者start_jump超越了当前位置可以跳的最远位置
            if(max_index<index[start_jump]){
                max_index=index[start_jump];//如果当前可以跳的更远,则更新max_index 的值
            }
            start_jump++; // 扫描最初始jump到当前位置可到的最大位置
        }
        if(start_jump >= index.size()){ // 若start_jump 大于等于 数组尾部,返回真
            return true;
        }
        return false;
    }
};
  • 可本地运行测试的完整代码:
#include <stdio.h>

#include <vector>
class Solution {
public:
    bool canJump(std::vector<int>& nums) {
        std::vector<int> index;
        for (int i = 0; i < nums.size(); i++){
        	index.push_back(i + nums[i]);
        }
        int jump = 0;
        int max_index = index[0];
        while(jump < index.size() && jump <= max_index){
        	if (max_index < index[jump]){
	        	max_index = index[jump];
	        }
        	jump++;
        }
        if (jump == index.size()){
        	return true;
        }
        return false;
    }
};

int main(){
	std::vector<int> nums;
	nums.push_back(2);
	nums.push_back(3);
	nums.push_back(1);
	nums.push_back(1);
	nums.push_back(4);
	Solution solve;
	printf("%d\n", solve.canJump(nums));
	return 0;
}

典例5、跳跃游戏2(hard)

  • 方法:(贪心)
  • 题目描述:
  • 贪心算法 python 贪心算法经典例题_数据结构与算法_12

  • 思路:要求跳跃的次数最小,那就在没有到达最终的尾部之前,只有往前不能走了的时候,不得不跳的时候,再跳
  • 贪心算法 python 贪心算法经典例题_贪心算法 python_13

  • 关于更新:pre_max_max_index,即是跳到最远的位置,出现最远的情况。
  • 贪心算法 python 贪心算法经典例题_贪心算法 python_14

  • LeetCode提交OJ测试链接:
  • OJ测试代码实现:
class Solution {
public:
    int jump(vector<int>& nums) {
        // 特殊情况
        if (nums.size()<2){ // 数组的长度小于2,不用跳跃,返回0
            return 0;
        }
        int current_max_index = nums[0]; // 当前位置可跳跃的最大值 
        int pre_max_max_index = nums[0]; // 当前位置 到 当前位置可跳跃的最大位置之间的(遍历各个位置过程中) 可跳跃到的最远位置
        int jump_min_count = 1;
        for(int i =1;i<nums.size();i++){
            if (i>current_max_index){ // 只有不能往后走了(还没到最后的尾部),才往后跳一次,而且是跳到最远的位置
                jump_min_count++;
                current_max_index = pre_max_max_index;// 更新可以跳到的最远位置数值
            }
            if(pre_max_max_index < nums[i]+i){ // 理解为跳的距离小于nums[i]+i的位置时
                pre_max_max_index = nums[i]+i;
            }
        }
        return jump_min_count;
    }
};
  • 可本地运行测试的完整代码:
#include <stdio.h>

#include <vector>
class Solution {
public:
    int jump(std::vector<int>& nums) {
    	if (nums.size() < 2){
	    	return 0;
	    }
        int current_max_index = nums[0];
        int pre_max_max_index = nums[0];
        int jump_min = 1;
        for (int i = 1; i < nums.size(); i++){
        	if (i > current_max_index){
        		jump_min++;
	        	current_max_index = pre_max_max_index;
	        }
        	if (pre_max_max_index < nums[i] + i){
       			pre_max_max_index = nums[i] + i;
        	}
        }
        return jump_min;
    }
};

int main(){
	std::vector<int> nums;
	nums.push_back(2);
	nums.push_back(3);
	nums.push_back(1);
	nums.push_back(1);
	nums.push_back(4);
	Solution solve;
	printf("%d\n", solve.jump(nums));
	return 0;
}

典例6、用最少数量的箭射击气球(medium)

class Solution {
public:
    int findMinArrowShots(vector<vector<int>>& points) {
    	if (points.size() == 0){return 0;} // 输入气球为空,返回0
    	sort(points.begin(), points.end(),[](const vector<int>& a, const vector<int>& b){return a[0]<b[0]; }); // 按照坐标的左端点排序,不用考虑左端点相同的情况
    	int shoot_num = 1; // 初始化弓箭个数
    	int shoot_begin = points[0][0];//points[i].first// 初始化射击区间,第一个区间就是第一个气球的坐标范围
    	int shoot_end = points[0][1]; //points[0].second
    	for (int i = 1; i < points.size(); i++){// 依次遍历各个气球区间
	    	if (points[i][0] <= shoot_end){//  若下一个区间的左端点<= 射击的范围右端点
	    		shoot_begin = points[i][0];// 合并缩小区间
    			if (shoot_end > points[i][1]){// 若同时下一个区间的右端点小于射击范围的右端点,为取公共部分,缩小射击区间
			    	shoot_end = points[i][1];
			    }
	    	}
	    	else{ // 如果下个气球坐标不在上个射击范围内,就是保证之前的气球都被击破的情况下,射击区间不能再更新,就要添加新的射击区间
	    		shoot_num++;// 射箭数 +1
	    		shoot_begin = points[i][0];// 新的射击区间就是下个气球的坐标范围
	    		shoot_end = points[i][1];// 
	    	}
	    }
	    return shoot_num;
    }
};
#include <stdio.h>

#include <algorithm>
#include <vector>

bool cmp(const std::pair<int, int> &a, const std::pair<int ,int> &b) {
    return a.first < b.first;
}

class Solution {
public:
    int findMinArrowShots(std::vector<std::pair<int, int> >& points) {
    	if (points.size() == 0){
	    	return 0;
	    }
    	std::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;
    }
};

int main(){
	std::vector<std::pair<int, int> > points;
	points.push_back(std::make_pair(10, 16));
	points.push_back(std::make_pair(2, 8));
	points.push_back(std::make_pair(1, 6));
	points.push_back(std::make_pair(7, 12));	
	Solution solve;
	printf("%d\n", solve.findMinArrowShots(points));
	return 0;
}

典例7、最优加油策略(hard)

  • 方法:(贪心、堆)
  • 题目描述:
  • 贪心算法 python 贪心算法经典例题_c++_19

  • 思路:要加油的次数最少,还要到达终点,那就在保证到达终点的情况下,只有在快要没油的时候去加油,而且每次加油,都在加油量最多的加油站停留加油
  • 贪心算法 python 贪心算法经典例题_leetcode_20

  • 找到耗尽初始油量之前能到的路径范围内,加油量最多的地方;不加油就无法到达后续的终点,所以就在必须加油路途上油量补充最大的地方加油。
  • 思路想清楚以后,难点还有想到构建一个最大堆,较容易的找到最大的加油量

贪心算法 python 贪心算法经典例题_c++_21

  • OJ测试代码实现:
class Solution:
public:
	int get_minimum_stop(int L, int P, std::vector<std::pair<int, int> > &stop){ // L为起点到终点的距离;P为起点初始的油量
		std::priority_queue<int> Q; // 存储油量的最大堆
		int result = 0; // 加油次数
		stop.push_back(std::make_pair(0, 0)); // 将终点作为一个停靠点,添加到stop数组
		//std::sort(stop.begin(), stop.end(), cmp);
		sort(points.begin(), points.end(),[](const vector<int>& a, const vector<int>& b){return a[0]<b[0]; }); //pair<加油站到终点的距离, 加油站的汽油量> ;以停靠点到终点的距离从大到小进行排序,
		for (int i = 0; i < stop.size(); i++){ // 遍历各个停靠点
			int dis = L - stop[i][0]; // 当前要走的距离就是当前距终点的距离(L减去下一个停靠点到终点的距离)
			while (!Q.empty() && P < dis){
				P += Q.top();
				Q.pop();
				result++;
			}
			if (Q.empty() && P < dis){
				return -1;
			}
			P = P - dis;
			L = stop[i][0]; // 更新L为当前停靠点到终点的距离
			Q.push(stop[i][1]);// 将当前停靠点的汽油量添加到最大堆
		}
		return result;
	}
  • 可本地运行测试的完整代码:
#include <stdio.h>

#include <vector>
#include <algorithm>
#include <queue>

bool cmp(const std::pair<int, int> &a, const std::pair<int ,int> &b) {
    return a.first > b.first; // pair<加油站到终点的距离, 加油站的汽油量>
}

int get_minimum_stop(int L, int P, std::vector<std::pair<int, int> > &stop){
	std::priority_queue<int> Q;
	int result = 0;
	stop.push_back(std::make_pair(0, 0));
	std::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 = P - dis;
		L = stop[i].first;
		Q.push(stop[i].second);
	}
	return result;
}

int main(){
	std::vector<std::pair<int, int> > stop;
	int N;
	int L;
	int P;
	int distance;
	int fuel;
	scanf("%d", &N);
	for (int i = 0; i < N; i++){
		scanf("%d %d", &distance, &fuel);
		stop.push_back(std::make_pair(distance, fuel));
	}
	scanf("%d %d", &L, &P);
	printf("%d\n", get_minimum_stop(L, P, stop));
	return 0;
}