【代码随想录算法训练营14期】- day 31 -35第八章 贪心算法

● 理论基础
● 455.分发饼干
● 376. 摆动序列
● 53. 最大子序和

455.分发饼干

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

思路

目标是尽可能满足越多数量的孩子,这里贪心策略,即每个孩子得到最小尺寸满足自己的胃口。
先把孩子胃口以及饼干尺寸从小到大排序,遍历饼干数组,当饼干尺寸满足孩子时,孩子数量加一,即再继续安排下一个孩子的饼干尺寸,循环直到饼干尺寸遍历完。
这里注意孩子的数量其实就是孩子胃口的下标

代码

class Solution {
public:
    int findContentChildren(vector<int>& g, vector<int>& s) {
        sort(g.begin(),g.end());
        sort(s.begin(),s.end());
        //小饼干满足小胃口
        int num = 0;
        for(int i = 0; i < s.size(); i++){
            if(num  < g.size() && g[num] <= s[i]){
                num++;
            }
        }
        return num;
    }
};

376. 摆动序列

如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为摆动序列。第一个差(如果存在的话)可能是正数或负数。少于两个元素的序列也是摆动序列。
例如, [1,7,4,9,2,5] 是一个摆动序列,因为差值 (6,-3,5,-7,3) 是正负交替出现的。相反, [1,4,7,2,5] 和 [1,7,4,5,5] 不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。
给定一个整数序列,返回作为摆动序列的最长子序列的长度。 通过从原始序列中删除一些(也可以不删除)元素来获得子序列,剩下的元素保持其原始顺序。

思路

这里摆动序列可以理解一个值是其局部最值,那么判断其左右两边一边大于0一边小于0即可;
具体条件可以左边小于等于0;或者大于等于0## 代码

class Solution {
public:
    int wiggleMaxLength(vector<int>& nums) {
        if(nums.size() <= 1){
            return nums.size();
        }
        int ans = 1;
        int pre = 0, cur = 0;
        for(int i = 1; i < nums.size(); i++){
            cur = nums[i] - nums[i - 1];
            if((pre <= 0 && cur > 0) || (pre >= 0 && cur < 0)){
                pre = cur;
                ans++;
            }
        }
        return ans;
    }
};

53. 最大子序和

思路

记录最大子序列和,给定一个tmp记录暂时序列的和,每计算一次要与答案比较是否大于ans
当tmp为负数代表加上现在这个数序列和为负,因此不要前面序列,对tmp赋值0重新计和

代码

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int ans = INT_MIN;
        int tmp = 0;
        for(int i = 0; i < nums.size(); i++){
            tmp += nums[i];
            if(tmp > ans){
                ans = tmp;
            }
            if(tmp < 0){
                tmp = 0;
            }
        }
        return ans;

    }
};

122. 买卖股票的最佳时机 II

给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。

在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。

返回 你能获得的 最大 利润 。

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

贪心算法只要明天的价格比今天高就买入;

代码

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int bonus = 0;
        for(int i = 1; i < prices.size(); i++){
            int tmp = prices[i] - prices[i - 1];
            if(tmp > 0){
                bonus += tmp; 
            }
        }
        return bonus;
    }
};

55. 跳跃游戏

给定一个非负整数数组 nums ,你最初位于数组的 第一个下标 。

数组中的每个元素代表你在该位置可以跳跃的最大长度。

判断你是否能够到达最后一个下标。

思路

能否到达最后一个下标,因为数组元素表示该位置可以跳跃的最大长度,因此只需计算整个数组可以跳跃的最大长度大于等于最后一个下标,不需要在意中间怎么走
设定范围变量range,对可到达范围的每个下标进行计算可达的最大范围,即所在下标+跳跃最大长度值和目前最大范围值比较,

代码

class Solution {
public:
    bool canJump(vector<int>& nums) {
        //最远的长度是否大于等于最后一个下标
        int range = 0;
        for(int i = 0; i <= range; i++){
            range = max(nums[i] + i , range);
            if(range >= nums.size() - 1){
                return true;
            }
        };
        return false;

    }
};

45. 跳跃游戏 II

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

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

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

思路

记录每一步能走的最大范围curRange,直到到范围终点preRange时记录步数加1,还要记录新的范围终点即现在的最大范围值

代码

class Solution {
public:
    int jump(vector<int>& nums) {
        if(nums.size() == 1) return 0;
        int ans = 0;
        int curRange = 0;
        int preRange = 0;
        for(int i = 0; i < nums.size(); i++){
            curRange = max(nums[i] + i , curRange);
            if(i == preRange){
                ans++;
                preRange = curRange;
            }
            if(preRange >= nums.size() - 1) break;
        }
        return ans;
    }
};

1005. K 次取反后最大化的数组和

思路

贪心算法,排序以后先把所有负数取为正数;
后续处理:1、k剩余次数,但是nums已经全为正数,这里判断最小正数用于取反2、k剩余次数,所有数组遍历完全(全为负),那么设定最后一个值为最小正数(麻烦
第二次写法,特别贪心每次赋值以后排序,对最小的值取反

代码

class Solution {
public:
    int largestSumAfterKNegations(vector<int>& nums, int k) {
        sort(nums.begin(),nums.end());
        int ans = 0,mintmp = nums.size() - 1,tag = 0;
        for(int i = 0; i < nums.size(); i++){
            //有值为0或者负数完成k次反转
            if(k == 0){
                break;
            }
            if(nums[i] == 0){
                tag = 1;
                break;
            }
            else if(nums[i] < 0){
                nums[i] = -1 * nums[i];
                k--;
            }
            else{
                if(i == 0){
                    mintmp = 0;
                }
                else{
                    if(nums[i] > nums[i - 1]){
                        mintmp = i - 1;
                    }
                    else{
                        mintmp = i;
                    }
                }
                break;
            } 
        }
        if(k % 2 == 1 && tag == 0){
            nums[mintmp] = -1 * nums[mintmp];
        }
        for(int i = 0; i < nums.size(); i++){
            ans += nums[i];
        }
        return ans;
    }
};
class Solution {
public:
    int largestSumAfterKNegations(vector<int>& nums, int k) {
        for(int i = 0; i < k; i++){
            sort(nums.begin(),nums.end());
            nums[0] = -1 * nums[0];
        }
        int ans = 0;
        for(int j = 0; j < nums.size(); j++){
            ans += nums[j];
        }
        return ans;
    }
};

134.加油站

思路

这里两个约束条件,油要满足回到起点,因此加油的总量必须大于等于消耗的总量;起点要满足从起点开始加油量大于等于消耗的量,一旦不满足,起点为下一个起点且前面的剩余值清零。

代码

class Solution {
public:
    int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
        //贪心算法
        int n = gas.size();
        int res = 0, start = 0, totalSum = 0;
        for(int i = 0; i < n; i++){
            res += gas[i] - cost[i];
            totalSum += gas[i] - cost[i];
            if(res < 0){
                start = i + 1;
                res = 0;
            }
        }
        if(totalSum < 0) return -1;
        return start;
    }
};

135. 分发糖果

n 个孩子站成一排。给你一个整数数组 ratings 表示每个孩子的评分。

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

每个孩子至少分配到 1 个糖果。
相邻两个孩子评分更高的孩子会获得更多的糖果。
请你给每个孩子分发糖果,计算并返回需要准备的 最少糖果数目 。

思路

重点在相邻两个孩子评分更高的孩子获得更高糖果;
所以比较的时候我可能会想到三个位置两两比较,但是这样处理可能没考虑三个的边界;
所以采用两次贪心,从左到右遍历,一旦右边i大于左边i-1,那么赋值右边权值+1;从右到左遍历,一旦左边大于右边,那么赋值右边的权重+1和之前遍历的值的最大值;

代码

class Solution {
public:
    int candy(vector<int>& ratings) {
        int n = ratings.size();
        vector<int> nums(n,1);
        for(int i = 1; i < n; i++){
            if(ratings[i] > ratings[i - 1]){
                nums[i] = nums[i - 1] + 1;
            }
        }
        for(int j = n - 2; j >= 0 ; j--){
            if(ratings[j] > ratings[j + 1]){
                nums[j] = max(nums[j + 1] + 1, nums[j]);
            }
        }
        int ans = 0;
        for(auto num : nums){
            ans += num;
        }
        return ans;

    }
};

406.根据身高重建队列

假设有打乱顺序的一群人站成一个队列,数组 people 表示队列中一些人的属性(不一定按顺序)。每个 people[i] = [hi, ki] 表示第 i 个人的身高为 hi ,前面 正好 有 ki 个身高大于或等于 hi 的人。

请你重新构造并返回输入数组 people 所表示的队列。返回的队列应该格式化为数组 queue ,其中 queue[j] = [hj, kj] 是队列中第 j 个人的属性(queue[0] 是排在队列前面的人)。

思路

先把二位数组中的身高排序,如果身高相同,序号小的在前面。
再按从身高大到小顺序依次放入,因为只有矮的人能看到前面高的,故放小的身高时,插入前面的位置都是比他高的

代码

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>> ans;
        for(int i = 0; i < people.size(); i++){
            int pos = people[i][1];
            ans.insert(ans.begin() + pos, people[i]);
        }
        return ans;
    }
};

452. 用最少数量的箭引爆气球

有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points ,其中points[i] = [xstart, xend] 表示水平直径在 xstart 和 xend之间的气球。你不知道气球的确切 y 坐标。

一支弓箭可以沿着 x 轴从不同点 完全垂直 地射出。在坐标 x 处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstart,xend, 且满足 xstart ≤ x ≤ xend,则该气球会被 引爆 。可以射出的弓箭的数量 没有限制 。 弓箭一旦被射出之后,可以无限地前进。

给你一个数组 points ,返回引爆所有气球所必须射出的 最小 弓箭数 。

思路

先把的开始范围从小到大排序,每次比较新的范围开始和上次范围的结束,如果新的起始点在上次范围终点外,那么数量增加,如果在上次范围内,那么新的范围终点要去这两次小的数;

代码

class Solution {
public:
    static bool cmp(const vector<int>& a,const vector<int>& b){
        return a[0] < b[0];
    }
    int findMinArrowShots(vector<vector<int>>& points) {
        sort(points.begin(), points.end(), cmp);
        int ans = 1;
        for(int i = 1; i < points.size(); i++){
            if(points[i][0] > points[i - 1][1]){
                ans++;
            }
            else{
                points[i][1] = min(points[i - 1][1],points[i][1]);
            }
        }
        return ans;
    }
};

435. 无重叠区间

给定一个区间的集合 intervals ,其中 intervals[i] = [starti, endi] 。返回 需要移除区间的最小数量,使剩余区间互不重叠 。

思路

移除重叠区间的最小数量,问题就是要求非重叠区间的最大个数,这里可以按右边边界从小到到排序,与下一个范围左边界比较,如果左边界大于右边界那么就说明不重叠,这么怎么保证不重叠最大呢,就是右边界排序了每次都是先把右边界先结束的进行比较,即向右覆盖最小的范围故数量最大

代码

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) {
        sort(intervals.begin(),intervals.end(),cmp);
        int count = 1;//非交叉区间个数
        int end = intervals[0][1];
        for(int i = 1; i < intervals.size(); i++){
            if(intervals[i][0] >= end){
                end = intervals[i][1];
                count++;
            }
        }
        return intervals.size() - count;
    }
};

763. 划分字母区间

给你一个字符串 s 。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。

注意,划分结果需要满足:将所有划分结果按顺序连接,得到的字符串仍然是 s 。

返回一个表示每个字符串片段的长度的列表。

思路

满足同一个字母最多出现在一个片段,因此需要查找每个字母的最远边界,遍历到前面所有字母最远边界时,划分;这里先遍历一次记录每个字母的最远边界;再次遍历时比较每个边界保存最大边界值当当前下标等于最大边界值时,保存字段数量

代码

class Solution {
public:
    vector<int> partitionLabels(string s) {
        vector<int> ans;
        int hash[27] = {0};
        for(int i = 0; i < s.size(); i++){
            hash[s[i] - 'a'] = i;
        }
        int num = 0, end = 0;
        for(int i = 0;i < s.size(); i++){
            num++;
            end = max(end, hash[s[i] - 'a']);
            if(i == end){
                ans.push_back(num);
                num = 0;
            }
        }
        return ans;
    }
};

56. 合并区间

思路

把区间开始从小到大排序,比较边界范围;

代码

class Solution {
public:
    static bool cmp(const vector<int>& a, const vector<int>& b){
        return a[0] < b[0];
    }
    vector<vector<int>> merge(vector<vector<int>>& intervals) {
        vector<vector<int>> ans;
        sort(intervals.begin(),intervals.end(),cmp);
        int start= intervals[0][0], end = intervals[0][1];
        for(int i = 1; i < intervals.size(); i++){
            if(intervals[i][0] > end){
                ans.push_back({start,end});
                start = intervals[i][0];
                end = intervals[i][1];
            }
            else{
                start = min(intervals[i][0],start);
                end = max(intervals[i][1],end);
            }
        }
        ans.push_back({start,end});
        return ans;
    }
};

738. 单调递增的数字

当且仅当每个相邻位数上的数字 x 和 y 满足 x <= y 时,我们称这个整数是单调递增的。

给定一个整数 n ,返回 小于或等于 n 的最大数字,且数字呈 单调递增 。

思路

这里要从个位数开始遍历以满足条件,每找到一个前一个数大于后一个数,那么就要前一个数建减1,后一个数赋值为9;但是不同同步赋值,因为会覆盖之前的数,故添加了一个tag标记哪些位数需要赋值;

代码

class Solution {
public:
    int monotoneIncreasingDigits(int n) {
        string s = to_string(n);
        int tag = s.size();
        for(int i = s.size() - 1; i > 0; i--){
            if(s[i - 1]  > s[i]){
                s[i - 1]--;
                tag = i;
            }
        }
        for(int i = tag; i < s.size(); i++){
            s[i] = '9';
        }
        return stoi(s);
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值