【数据结构与算法-贪心算法经典例题汇总】
- 典例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)
- 方法:(排序–>贪心)
- 题目描述:
- 思路:
- 算法流程分析:
- LeetCode提交OJ测试链接:
- OJ测试代码实现:
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)
- 方法:(贪心)
- 题目描述:
- 思路分析:
- 归纳:
- 使用状态机转换:(相比于单纯写 if - else ,架构上更加清晰)
- 对于贪心算法,最好先找几个例子,把思路确定下来,再整理代码。
- 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)
- 方法:(贪心&栈)
- 题目描述:
- 思考:
假如对于1432219,只去掉一个数值(k=1)时,应该去掉什么数值?
若 k > 1, 则应该按照什么样的顺序与策略进行逐渐的删除数字,得到最小的结果? - 思路:
- 栈演示:
- 补充:
数字转字符: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)
- 方法:(贪心)
- 题目描述:
- 分析:
- 思路:
- LeetCode提交OJ测试链接:
- OJ测试代码实现:
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)
- 方法:(贪心)
- 题目描述:
- 思路:要求跳跃的次数最小,那就在没有到达最终的尾部之前,只有往前不能走了的时候,不得不跳的时候,再跳 。
- 关于更新:pre_max_max_index,即是跳到最远的位置,出现最远的情况。
- 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)
- 方法:(排序&贪心)
- 题目描述:
- 思路:
- 排序后的模拟射击:
- LeetCode提交OJ测试链接:
- OJ测试代码实现:
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;
}
};
- 可本地运行测试的完整代码:
- 涉及 ’ std::pair ’ 的用法,参考:
class template std::pair.
#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)
- 方法:(贪心、堆)
- 题目描述:
- 思路:要加油的次数最少,还要到达终点,那就在保证到达终点的情况下,只有在快要没油的时候去加油,而且每次加油,都在加油量最多的加油站停留加油 。
- 找到耗尽初始油量之前能到的路径范围内,加油量最多的地方;不加油就无法到达后续的终点,所以就在必须加油路途上油量补充最大的地方加油。
- 思路想清楚以后,难点还有想到构建一个最大堆,较容易的找到最大的加油量
- 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;
}