【贪心算法】刷刷刷刷刷刷题(上)

供自己复习,一篇10题左右

1.分发饼干

假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。

对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。

示例 1:

  • 输入: g = [1,2,3], s = [1,1]
  • 输出: 1 解释:你有三个孩子和两块小饼干,3 个孩子的胃口值分别是:1,2,3。虽然你有两块小饼干,由于他们的尺寸都是 1,你只能让胃口值是 1 的孩子满足。所以你应该输出 1。

这里的局部最优就是大饼干喂给胃口大的,充分利用饼干尺寸喂饱一个,全局最优就是喂饱尽可能多的小孩

仔细阅读题目的要求,你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。

所以必须是先遍历胃口(孩子),给再遍历饼干

不能颠倒!!!!

cpp 复制代码
class Solution {
public:
    int findContentChildren(vector<int>& g, vector<int>& s) {
        sort(g.begin(), g.end());
        sort(s.begin(), s.end());
        int index = s.size() - 1;
        int result = 0;
        for(int i = g.size() - 1; i >= 0; i --){
            if(index >= 0 && g[i] <= s[index]){
                result  ++;
                index --;
            }
        }
        return result;
    }
};

2.分发糖果

老师想给孩子们分发糖果,有 N 个孩子站成了一条直线,老师会根据每个孩子的表现,预先给他们评分。

你需要按照以下要求,帮助老师给这些孩子分发糖果:

  • 每个孩子至少分配到 1 个糖果。
  • 相邻的孩子中,评分高的孩子必须获得更多的糖果。

那么这样下来,老师至少需要准备多少颗糖果呢?

示例 1:

  • 输入: [1,0,2]
  • 输出: 5
  • 解释: 你可以分别给这三个孩子分发 2、1、2 颗糖果。

先确定右边评分大于左边的情况(也就是从前向后遍历)
再确定左边大于右边的情况,但是此时不能再从前向后遍历了!!

如图的解释,判断左边大于右边,从前向后遍历的时候,得出的candyVec序列是1 2 1 2 1 1 1

如果还是从前向后遍历,判断右边大于左边,就会得到 1 2 1 2 2 2 2 的序列,从评分为5的孩子看来,应该比4高但是却没有得到更多的糖

根本原因就是,图上的3 5 4,判断加不加糖,左和右是岔开的,不能一样的遍历判断

这样逻辑会出问题。

第二个,就是从后向前遍历的时候,candVec[i]的选取问题,不能直接取candyVec[i + 1] + 1,因为有可能candyVec本身就比他大,再加的话不满足题目要求的最少糖果,所以candyVec[i] = max(candyVec[i], candyVec[i + 1] + 1)

cpp 复制代码
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;
    }
};

3.跳跃游戏I

给你一个非负整数数组 nums ,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。判断你是否能够到达最后一个下标,如果可以,返回 true ;否则,返回 false

题目只关心能不能跳到终点,所以不用关系要跳几步,只要最后能覆盖住终点就行

贪心算法局部最优解:每次取最大跳跃步数(取最大覆盖范围),整体最优解:最后得到整体最大覆盖范围,看是否能到终点

定义cover为每次能跳的步长,跳完当前步长和,步跳完当前步长后能跳的步长,做对比,取最大的!

cover可能会大于数组长度,所以要及时return!!

cpp 复制代码
class Solution {
public:
    bool canJump(vector<int>& nums) {
        int cover = 0;
        if (nums.size() == 1) return true; 
        for (int i = 0; i <= cover; i++) { 
            cover = max(i + nums[i], cover);
            if (cover >= nums.size() - 1) return true; 
        }
        return false;
    }
};

4.跳跃游戏II

给定一个长度为 n0 索引 整数数组 nums。初始位置为 nums[0]

每个元素 nums[i] 表示从索引 i 向前跳转的最大长度。换句话说,如果你在 nums[i] 处,你可以跳转到任意 nums[i + j] 处:

  • 0 <= j <= nums[i]
  • i + j < n

返回到达 nums[n - 1] 的最小跳跃次数。生成的测试用例可以到达 nums[n - 1]

不能真的能跳多远就跳多远,那样就不知道下一步最远能跳到哪里了。
所以真正解题的时候,要从覆盖范围出发,不管怎么跳,覆盖范围内一定是可以跳到的,以最小的步数增加覆盖范围,覆盖范围一旦覆盖了终点,得到的就是最少步数!

这里需要统计两个覆盖范围,当前这一步的最大覆盖和下一步最大覆盖

如果移动下标达到了当前这一步的最大覆盖最远距离了,还没有到终点的话,那么就必须再走一步来增加覆盖范围,直到覆盖范围覆盖了终点。

通俗点说,
第一步是要知道当前最远能跳到哪里,这里只需要知道最远能跳到哪里,中间没有操作
第二步是要知道以当前的跳的基础,下一步能跳到哪里,虽然是以当前的跳为基础,但是这里是个范围,在第一步没跳到第一步落地这个范围里面,找到第二步的最大覆盖
更新:当前覆盖的更新,是拿每一次next的值去更

cpp 复制代码
class Solution {
public:
    int jump(vector<int>& nums) {
        if(nums.size() <= 1) return 0;
        int cur = 0;
        int next = 0;
        int ans = 0;
        for(int i = 0; i < nums.size(); i ++){
            next = max(nums[i] + i, next);
            if(i == cur){
                ans ++;
                cur = next;
                if(next >= nums.size() - 1) break;
            }
        }
        return ans;
    }
};

5.合并区间

给出一个区间的集合,请合并所有重叠的区间。

示例 1:

  • 输入: intervals = [[1,3],[2,6],[8,10],[15,18]]
  • 输出: [[1,6],[8,10],[15,18]]
  • 解释: 区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
cpp 复制代码
class Solution {
public:
    vector<vector<int>> merge(vector<vector<int>>& intervals) {
        sort(intervals.begin(), intervals.end(), [](const vector<int> a, const vector<int> b){return a[0] < b[0];});
        vector<vector<int>> result;
        result.push_back(intervals[0]);
        for(int i = 0; i < intervals.size(); i ++){
            if(intervals[i][0] <= result.back()[1]){ // 区间重叠
                result.back()[1] = max(intervals[i][1], result.back()[1]);
            }
            else{
                result.push_back(intervals[i]); // 区间不重叠 
            }
        }
        return result;
    }
};

6.无重叠区间

给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。

注意: 可以认为区间的终点总是大于它的起点。 区间 [1,2] 和 [2,3] 的边界相互"接触",但没有相互重叠。

示例 1:

  • 输入: [ [1,2], [2,3], [3,4], [1,3] ]
  • 输出: 1
  • 解释: 移除 [1,3] 后,剩下的区间没有重叠。

按照右边界排序!!!

cpp 复制代码
class Solution {
public:
    static bool cmp(const vector<int> a, const vector<int> b){
        return a[1] < b[1];
    }
    int eraseOverlapIntervals(vector<vector<int>>& intervals) {
        if(intervals.size() == 0) return 0;
        sort(intervals.begin(), intervals.end(), cmp);
        int end = intervals[0][1];
        int count = 1;
        for(int i = 1; i < intervals.size(); i ++){
            if(intervals[i][0] >= end){
                count ++;
                end = intervals[i][1];
            }
        }
        return intervals.size() - count;
    }
};

7.划分字母区间

字符串 S 由小写字母组成。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。返回一个表示每个字符串片段的长度的列表。

示例:

  • 输入:S = "ababcbacadefegdehijhklij"
  • 输出:[9,7,8] 解释: 划分结果为 "ababcbaca", "defegde", "hijhklij"。 每个字母最多出现在一个片段中。 像"ababcbacadefegde", "hijhklij" 的划分是错误的,因为划分的片段数较少。

提示:

  • S的长度在[1, 500]之间。
  • S只包含小写字母 'a' 到 'z'

在遍历的过程中相当于是要找每一个字母的边界,如果找到之前遍历过的所有字母的最远边界,说明这个边界就是分割点了。此时前面出现过所有字母,最远也就到这个边界了。

可以分为如下两步:

  • 统计每一个字符最后出现的位置
  • 从头遍历字符,并更新字符的最远出现下标,如果找到字符最远出现位置下标和当前下标相等了,则找到了分割点
cpp 复制代码
class Solution {
public:
    vector<int> partitionLabels(string s) {
        // 更新每个字母出现的最远下标
        // 区间内更新其中每个字母出现的最远下标,i等于这个下标的时候片段+1
        int hash[27] = {0};//hash数组里面是不同字母对应的ascll码的最远下标 
        for(int i = 0; i < s.size(); i ++){
            hash[s[i] - 'a'] = i;
        }
        vector<int> result;
        int left = 0;
        int right = 0;
        for(int i = 0; i < s.size(); i ++){
            right = max(right, hash[s[i] - 'a']);
            if(i == right){
                result.push_back(right - left + 1);
                left = i + 1;
            }
        }
        return result;
    }
};

8.加油站

在一条环路上有 n 个加油站,其中第 i 个加油站有汽油 gas[i] 升。

你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。

给定两个整数数组 gas 和 cost ,如果你可以按顺序绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1 。如果存在解,则 保证 它是 唯一 的。

直接从全局进行贪心选择,情况如下:

  • 情况一:如果gas的总和小于cost总和,那么无论从哪里出发,一定是跑不了一圈的
  • 情况二:rest[i] = gas[i]-cost[i]为一天剩下的油,i从0开始计算累加到最后一站,如果累加没有出现负数,说明从0出发,油就没有断过,那么0就是起点。
  • 情况三:如果累加的最小值是负数,汽车就要从非0节点出发,从后向前,看哪个节点能把这个负数填平,能把这个负数填平的节点就是出发节点。

首先遍历计算每一个加油站点留下来的rest

然后rest累加,并且同时关注这个累加的curSum是否小于0,小于0就记录给min,当遍历完之后,可以得到curSum的值,还有累加过程中最小的一个和min

如果curSum小于0,情况一

如果min >= 0,情况二

情况三,min的值就有用了,之前记录了最小的和,现在从后往前,一个个加给他,

当从后向前进行遍历时,实际上是在尝试找到一个点,从该点出发(不论是向前还是向后)都能保证剩余油量始终大于等于零。

cpp 复制代码
class Solution {
public:
    int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
        int curSum = 0;
        int min = 0;
        for(int i = 0; i < gas.size(); i ++){
            int rest = gas[i] - cost[i];
            curSum += rest;
            if(curSum < min){
                min = curSum;
            }
        }
        if(curSum < 0) return -1;
        if(min >= 0) return 0;
        for(int i = gas.size() - 1; i >= 0; i --){
            int rest = gas[i] - cost[i];
            min += rest;
            if(min >= 0) return i;
        }
        return -1;
    }
};
相关推荐
XUE_DING_E5 分钟前
Educational Codeforces Round 171
算法
Patience to do15 分钟前
Android Studio项目(算法计算器)
android·算法·android studio
这题怎么做?!?31 分钟前
模板方法模式
开发语言·c++·算法
DdddJMs__1352 小时前
C语言 | Leetcode C语言题解之第517题超级洗衣机
c语言·leetcode·题解
边疆.2 小时前
C++类和对象 (中)
c语言·开发语言·c++·算法
binqian3 小时前
【K8S】kubernetes-dashboard.yaml
算法·贪心算法
Wils0nEdwards3 小时前
Leetcode 合并 K 个升序链表
算法·leetcode·链表
Tisfy3 小时前
LeetCode 3211.生成不含相邻零的二进制字符串:二进制枚举+位运算优化
算法·leetcode·二进制·题解·枚举·位运算
好看资源平台3 小时前
深入理解所有权与借用——借用与生命周期管理
开发语言·算法·rust