【算法学习】贪心算法篇:到底怎么贪啊

贪心算法学习

预备知识:贪心法

遵循某种规律,不断贪心地选取当前最优策略的算法设计方法

局部最优等价于全局最优

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;
    }
};

下期预告:递归、回溯与分治

  • 15
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
从零开始学习贪心算法需要掌握以下几个点: 1. 贪心算法的定义:贪心算法是指在问题求解过程中,总是做出当前看来最好的选择,而不考虑整体最优解。心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关。 2. 贪心算法的解题步骤:建立数学模型描述问题,将问题分解为若干个子问题,对每个子问题求解得到局部最优解,将局部最优解合成原问题的最优解。 3. 贪心算法与动态规划的关系:贪心算法是动态规划的一种特例。贪心算法不需要知道一个节点所有子树的情况,只需要选择当前问题的最优解路径,一直走到底即可。而动态规划需要自底向上构造子问题的解。 4. 活动选择问题是贪心算法的一个经典例子。在活动选择问题中,需要在同一天使用同一个教室举行多个活动,要求选择尽可能多的活动不冲突地进行。 综上所述,从零开始学习贪心算法,要理解贪心算法的定义和特点,掌握贪心算法的解题步骤,了解贪心算法与动态规划的关系,并通过经典例子如活动选择问题来实践和加深对贪心算法的理解。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [从0开始学贪心算法](https://blog.csdn.net/weixin_45024585/article/details/107515191)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [leetcode338-leetcode:从零开始](https://download.csdn.net/download/weixin_38750644/19950444)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [从零开始学贪心算法](https://blog.csdn.net/qq_32400847/article/details/51336300)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值