当前位置: 首页 > article >正文

DAY29|贪心算法Part03|LeetCode:134. 加油站、135. 分发糖果、860.柠檬水找零、406.根据身高重建队列

目录

LeetCode:134. 加油站

基本思路

C++代码

LeetCode:135. 分发糖果

基本思路

C++代码

LeetCode:860.柠檬水找零

基本思路

C++代码

LeetCode:406.根据身高重建队列

基本思路

C++代码


LeetCode:134. 加油站

力扣代码链接

文字讲解:LeetCode:134. 加油站

视频讲解:贪心算法,得这么加油才能跑完全程!

基本思路

        首先如果总油量减去总消耗大于等于零那么一定可以跑完一圈,说明 各个站点的加油站 剩油量rest[i]相加一定是大于等于零的。

        每个加油站的剩余量rest[i]为gas[i] - cost[i]。i从0开始累加rest[i],和记为curSum,一旦curSum小于零,说明[0, i]区间都不能作为起始位置,因为这个区间选择任何一个位置作为起点,到i这里都会断油,那么起始位置从i+1算起,再从0计算curSum。

        那么局部最优:当前累加rest[i]的和curSum一旦小于0,起始位置至少要是i+1,因为从i之前开始一定不行。全局最优:找到可以跑一圈的起始位置

C++代码

class Solution {
public:
    int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
        int curSum = 0;
        int totalSum = 0;
        int start = 0;
        for (int i = 0; i < gas.size(); i++) {
            curSum += gas[i] - cost[i];
            totalSum += gas[i] - cost[i];
            if (curSum < 0) {   // 当前累加rest[i]和 curSum一旦小于0
                start = i + 1;  // 起始位置更新为i+1
                curSum = 0;     // curSum从0开始
            }
        }
        if (totalSum < 0) return -1; // 说明怎么走都不可能跑一圈了
        return start;
    }
};

LeetCode:135. 分发糖果

力扣代码链接

文字讲解:LeetCode:135. 分发糖果

视频讲解:贪心算法,两者兼顾很容易顾此失彼!

基本思路

        这道题目一定是要确定一边之后,再确定另一边,例如比较每一个孩子的左边,然后再比较右边,如果两边一起考虑一定会顾此失彼

        先确定右边评分大于左边的情况(也就是从前向后遍历)。

        此时局部最优:只要右边评分比左边大,右边的孩子就多一个糖果,全局最优:相邻的孩子中,评分高的右孩子获得比左边孩子更多的糖果

// 从前向后
for (int i = 1; i < ratings.size(); i++) {
    if (ratings[i] > ratings[i - 1]) candyVec[i] = candyVec[i - 1] + 1;
}

        再确定左孩子大于右孩子的情况(从后向前遍历),遍历顺序这里有同学可能会有疑问,为什么不能从前向后遍历呢?

        因为 rating[5]与rating[4]的比较 要利用上 rating[5]与rating[6]的比较结果,所以 要从后向前遍历。如果从前向后遍历,rating[5]与rating[4]的比较 就不能用上 rating[5]与rating[6]的比较结果了 。所以确定左孩子大于右孩子的情况一定要从后向前遍历!

        如果 ratings[i] > ratings[i + 1],此时candyVec[i](第i个小孩的糖果数量)就有两个选择了,一个是candyVec[i + 1] + 1(从右边这个加1得到的糖果数量),一个是candyVec[i](之前比较右孩子大于左孩子得到的糖果数量)。

        那么又要贪心了,局部最优:取candyVec[i + 1] + 1 和 candyVec[i] 最大的糖果数量,保证第i个小孩的糖果数量既大于左边的也大于右边的。全局最优:相邻的孩子中,评分高的孩子获得更多的糖果。

// 从后向前
for (int i = ratings.size() - 2; i >= 0; i--) {
    if (ratings[i] > ratings[i + 1] ) {
        candyVec[i] = max(candyVec[i], candyVec[i + 1] + 1);
    }
}

C++代码

class Solution {
public:
    int candy(vector<int>& ratings) {
        vector<int> candyVec(ratings.size(), 1);
        // 从前向后
        for (int i = 1; i < ratings.size(); i++) {
            if (ratings[i] > ratings[i - 1]) candyVec[i] = candyVec[i - 1] + 1;
        }
        // 从后向前
        for (int i = ratings.size() - 2; i >= 0; i--) {
            if (ratings[i] > ratings[i + 1] ) {
                candyVec[i] = max(candyVec[i], candyVec[i + 1] + 1);
            }
        }
        // 统计结果
        int result = 0;
        for (int i = 0; i < candyVec.size(); i++) result += candyVec[i];
        return result;
    }
};

LeetCode:860.柠檬水找零

力扣代码链接

文字讲解:LeetCode:860.柠檬水找零

视频讲解:贪心算法,看上去复杂,其实逻辑都是固定的!

基本思路

        仔细一琢磨就会发现,可供我们做判断的空间非常少!无外乎就三种情况:

  • 情况一:账单是5,直接收下。
  • 情况二:账单是10,消耗一个5,增加一个10
  • 情况三:账单是20,优先消耗一个10和一个5,如果不够,再消耗三个5

        账单是20的情况,为什么要优先消耗一个10和一个5呢?因为美元10只能给账单20找零,而美元5可以给账单10和账单20找零,美元5更万能!

        所以局部最优:遇到账单20,优先消耗美元10,完成本次找零。全局最优:完成全部账单的找零。

C++代码

class Solution {
public:
    bool lemonadeChange(vector<int>& bills) {
        int five = 0, ten = 0, twenty = 0;
        for (int bill : bills) {
            // 情况一
            if (bill == 5) five++;
            // 情况二
            if (bill == 10) {
                if (five <= 0) return false;
                ten++;
                five--;
            }
            // 情况三
            if (bill == 20) {
                // 优先消耗10美元,因为5美元的找零用处更大,能多留着就多留着
                if (five > 0 && ten > 0) {
                    five--;
                    ten--;
                    twenty++; // 其实这行代码可以删了,因为记录20已经没有意义了,不会用20来找零
                } else if (five >= 3) {
                    five -= 3;
                    twenty++; // 同理,这行代码也可以删了
                } else return false;
            }
        }
        return true;
    }
};

LeetCode:406.根据身高重建队列

力扣代码链接

文字讲解:LeetCode:406.根据身高重建队列

视频讲解:贪心算法,不要两边一起贪,会顾此失彼

基本思路

        本题有两个维度,h和k,看到这种题目一定要想如何确定一个维度,然后再按照另一个维度重新排列。和上面的分发糖果的思想很像。如果两个维度一起考虑一定会顾此失彼

        本题中又两个维度k,h,究竟先按h排序呢,还是先按照k排序呢?

        如果按照k来从小到大排序,排完之后,会发现k的排列并不符合条件,身高也不符合条件,两个维度哪一个都没确定下来。那么按照身高h来排序呢,身高一定是从大到小排(身高相同的话则k小的站前面),让高个子在前面。此时我们可以确定一个维度了,就是身高,前面的节点一定都比本节点高!那么只需要按照k为下标重新插入队列就可以了,如下图所示:

局部最优:优先按身高高的people的k来插入。插入操作过后的people满足队列属性

全局最优:最后都做完插入操作,整个队列满足题目队列属性

回归本题,整个插入过程如下:

        排序完的people: [[7,0], [7,1], [6,1], [5,0], [5,2], [4,4]]

        插入的过程:

  • 插入[7,0]:[[7,0]]
  • 插入[7,1]:[[7,0],[7,1]]
  • 插入[6,1]:[[7,0],[6,1],[7,1]]
  • 插入[5,0]:[[5,0],[7,0],[6,1],[7,1]]
  • 插入[5,2]:[[5,0],[7,0],[5,2],[6,1],[7,1]]
  • 插入[4,4]:[[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]]

        此时就按照题目的要求完成了重新排列。

C++代码

// 版本一
class Solution {
public:
    static bool cmp(const vector<int>& a, const vector<int>& b) {
        if (a[0] == b[0]) return a[1] < b[1];
        return a[0] > b[0];
    }
    vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
        sort (people.begin(), people.end(), cmp);
        vector<vector<int>> que;
        for (int i = 0; i < people.size(); i++) {
            int position = people[i][1];
            que.insert(que.begin() + position, people[i]);
        }
        return que;
    }
};

        当然使用vector是非常费时的,C++中vector(可以理解是一个动态数组,底层是普通数组实现的)如果插入元素大于预先普通数组大小,vector底部会有一个扩容的操作,即申请两倍于原先普通数组的大小,然后把数据拷贝到另一个更大的数组上。

        所以使用vector(动态数组)来insert,是费时的,插入再拷贝的话,单纯一个插入的操作就是O(n^2)了,甚至可能拷贝好几次,就不止O(n^2)了。而使用链表,插入效率比vector高的多,可以大大节省时间。

// 版本二
class Solution {
public:
    // 身高从大到小排(身高相同k小的站前面)
    static bool cmp(const vector<int>& a, const vector<int>& b) {
        if (a[0] == b[0]) return a[1] < b[1];
        return a[0] > b[0];
    }
    vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
        sort (people.begin(), people.end(), cmp);
        list<vector<int>> que; // list底层是链表实现,插入效率比vector高的多
        for (int i = 0; i < people.size(); i++) {
            int position = people[i][1]; // 插入到下标为position的位置
            std::list<vector<int>>::iterator it = que.begin();
            while (position--) { // 寻找在插入位置
                it++;
            }
            que.insert(it, people[i]);
        }
        return vector<vector<int>>(que.begin(), que.end());
    }
};

http://www.kler.cn/a/397912.html

相关文章:

  • 【日常记录-Git】git log
  • 学习threejs,使用第一视角控制器FirstPersonControls控制相机
  • ZooKeeper集群指南-新增节点配置
  • Python学习------第八天
  • 基于Spring Boot的电子商务系统设计
  • (附项目源码)nodejs开发语言,212 个性化音乐推荐系统的设计与实现,计算机毕设程序开发+文案(LW+PPT)
  • 论文 | The Capacity for Moral Self-Correction in LargeLanguage Models
  • 蓝队基础2 -- 外部威胁与攻击面
  • 报错ImportError: Pandas requires version ‘3.0.7‘ or newer of ‘openpyxl‘
  • pom中无法下载下来的类外部引用只给一个jar的时候
  • ArkUI---常用组件---切换按钮 (Toggle)
  • 重置docker版本的octoprint管理员账号密码
  • ECharts 创建图表示例
  • 30 秒!用通义灵码画 SpaceX 星链发射流程图
  • Android 开启流量节省状态会使热点与网络共享无法打开
  • POI word转pdf乱码问题处理
  • Spring框架之命令模式 (Command Pattern)
  • RestSharp基本使用方法
  • 2024-11-16-机器学习方法:无监督学习(1) 聚类(上)
  • 快速上手:Docker 安装详细教程(适用于 Windows、macOS、Linux)
  • 【循环测试试题3】小X与数字三角形
  • 普通电脑上安装属于自己的Llama 3 大模型和对话客户端
  • ‘v-scale-screen‘使用(Vue框架的大屏幕自适应组件)
  • # SpringSecutrity学习
  • 遥测数据采集工具Grafana Alloy
  • Redis系列之底层数据结构ZipList