贪心算法LEETCODE集锦(包含区间问题)

15 篇文章 3 订阅

455.分发饼干

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

对每个孩子 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。
示例 2:

输入: g = [1,2], s = [1,2,3]
输出: 2
解释:
你有两个孩子和三块小饼干,2个孩子的胃口值分别是1,2。
你拥有的饼干数量和尺寸都足以让所有孩子满足。
所以你应该输出2.

分析:

确定基本策略:大尺寸的饼干既可以满足胃口大的孩子也可以满足胃口小的孩子,那么就应该优先满足胃口大的。

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

  • 先将饼干和小孩胃口从大到小进行排序
  • 然后在将胃口和饼干进行遍历,并看饼干是否满足小孩的胃口
class Solution {
public:
   int findContentChildren(vector<int>& g, vector<int>& s) {
       sort(g.begin(), g.end(), [](const int& a, const int& b){
           return a>b;
       });
       sort(s.begin(), s.end(), [](const int& a, const int& b){
           return a>b;
       });
       int count = 0;
       int kek = 0;
       int kid = 0;
       while(kek < s.size() && kid < g.size()){
           if(s[kek] >= g[kid]){
               count++;
               kek++;
               kid++;
           }else{
                kid++;
           }
       }
       return count;
   }
};

时间复杂度分析:
用了快速排序:O(NlogN)
对数组进行了一次遍历O(N)
总体为:O(NlogN)

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

给定一个整数数组 A,我们只能用以下方法修改该数组:我们选择某个索引 i 并将 A[i] 替换为 -A[i],然后总共重复这个过程 K 次。(我们可以多次选择同一个索引 i。)

以这种方式修改数组后,返回数组可能的最大和。
示例 1:

输入:A = [4,2,3], K = 1
输出:5
解释:选择索引 (1,) ,然后 A 变为 [4,-2,3]。

示例 2:

输入:A = [3,-1,0,2], K = 3
输出:6
解释:选择索引 (1, 2, 2) ,然后 A 变为 [3,1,0,2]。

示例 3:

输入:A = [2,-3,-1,5,-4], K = 2
输出:13
解释:选择索引 (1, 4) ,然后 A 变为 [2,3,-1,5,4]。

思路:
很自然想到,
局部最优: 将负数变为正数,当前值最大。首先对绝对值大的负数进行操作
全局最优: 整个数组的总和值最大。
从局部可以推到出全局最优,且无反例,可以使用贪心算法。
如果全是正数的情况,又可以看做是另一个贪心情况。
局部全局最优: 将最小值反复正负变换,可以使整体最大。

  • 将其按照绝对值从大到小进行排序
  • 将负数变为正数
  • 将最小值反复正负变换,直至k==0
  • 求和
class Solution {
public:
    int largestSumAfterKNegations(vector<int>& A, int K) {
        sort(A.begin(),A.end(),[](const int& a, const int& b){
            return abs(a)>abs(b);
        });

        for(int i = 0; i < A.size(); i++){
            if(K==0){break;}
            if(A[i]<0){
                K--;
                A[i] = -A[i];
            }
        }

        while(K--){A[A.size()-1]*=-1;}
        int sum = 0;
        for(int e:A){sum+=e;}
        return sum;

    }
};

376. 摆动序列

如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为摆动序列。第一个差(如果存在的话)可能是正数或负数。少于两个元素的序列也是摆动序列。

例如, [1,7,4,9,2,5] 是一个摆动序列,因为差值 (6,-3,5,-7,3) 是正负交替出现的。相反, [1,4,7,2,5] 和 [1,7,4,5,5] 不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。

给定一个整数序列,返回作为摆动序列的最长子序列的长度。 通过从原始序列中删除一些(也可以不删除)元素来获得子序列,剩下的元素保持其原始顺序。

示例 1:

输入: [1,7,4,9,2,5]
输出: 6
解释: 整个序列均为摆动序列。]。

示例 2:

输入: [1,17,5,10,13,15,10,5,16,8]
输出: 7
解释: 这个序列包含几个长度为 7 摆动序列,其中一个可为[1,17,10,13,10,16,8]。

示例 3:

示例 3:
输入: [1,2,3,4,5,6,7,8,9]
输出: 2

思路:
统计连续一上一下的数的个数,即波峰波谷的数量
局部最优: 将数组中连续上升或下降的中间部分忽略掉,只关心波峰和波谷
从局部可以推到出全局最优,且无反例,可以使用贪心算法。
如果全是正数的情况,又可以看做是另一个贪心情况。

  • 定义两个int型数,记录前一个差值和后一个差值,以此来判断上升还是下降
  • 关键点: 如何将连续递增或减中间部分省略掉
    • 可以将单调部分continue,在波峰或波谷出在更新pre
class Solution {
public:
    int wiggleMaxLength(vector<int>& nums) {
        if(nums.empty()){
            return 0;
        }
        int count = 1;
        int pre = 0;
        for(int i = 0; i < nums.size()-1; i++){
            int cur = nums[i+1] - nums[i];
            if(pre <=0 && cur > 0){
                count++;
                pre = cur;
            } else if(pre >=0 && cur < 0){
                count++;
                pre = cur;
            }else{
                continue;
            }
        }
        return count;
    }
};

406 根据身高重建队列

题目描述
思路:

二维数组,同时对两个维度考量,思路会有点混乱,所以我们现需要确定一个维度。
如果我们确定对维度k进行处理。会发现,这既没有解决身高大小问题,也不能解决前面有多少个人比当前元素高这两个问题
所以选择对维度h进行处理,对身高进行从大到小排序,如果身高相等的情况,则对k进行判断,k从小到大
排序完成后,按维度k,进行插入处理,如果k为0,则插入头部,k为2,则插入引索为2的位置
可以看出,该方法需要进行频繁的插入处理。这时如果还用vector,则时间复杂度会特别大
所以,我们应该使用更适合频繁增删的链表。

List的底层是链表实现的,增删效率高

使用vector进行增删的方法

class Solution {
public:
    vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
        sort(people.begin(),people.end(),[](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>> res;
        for(int i = 0; i < people.size(); i++){
            res.insert(res.begin()+people[i][1],people[i]);
        }
        return res;
    }
};

使用链表进行增删的方法:

class Solution {
public:
    vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
        sort(people.begin(),people.end(),[](const vector<int>& a, const vector<int>& b){
            if(a[0]==b[0]){
                return a[1]<b[1];
            }
            return a[0]>b[0];
        });
        list<vector<int>> res;
        for(int i = 0; i < people.size(); i++){
            int pos = people[i][1];
            auto it = res.begin();
            //std::list<vector<int>>::iterator it = res.begin();
            while(pos){
                it++;
                pos--;
            }
            res.insert(it,people[i]);
        }
        vector<vector<int>> result(res.begin(),res.end());
        return result;
    }
};

区间问题

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

尽可能用一只箭射破更多的气球。这样可以联想到贪心算法。
局部最优:用一支箭尽可能多的射破气球
全局最优:局部最优可以推出全局最优,用最少的箭引爆所有气球。而且没有反例
第一箭必须射破,右边界最小值的气球,所以第一箭为右边界最小值的气球的有边界值,并射破其沿途气球
然后再射还未引爆的右边界最小值的气球的有边界值。射破其沿途气球
重复上述步骤,直至所有气球引爆

class Solution {
public:
    int findMinArrowShots(vector<vector<int>>& points) {
        vector<bool> used(points.size(),false);
        sort(points.begin(),points.end(),[](const vector<int>& a, const vector<int>& b){
            return a[a.size()-1] < b[b.size()-1];
        });
        int count = 0;
        for(int i = 0; i < points.size(); i++){
            if(used[i]==true){
                continue;
            }
            count++;

            int shoot = points[i][points[i].size()-1];
            int j = i + 1;
            while(1){
                if(j> points.size()-1){
                    break;
                }
                if(shoot >= points[j][0]){
                    used[j] = true;
                }else{
                    break;
                }
                j++;
            }
        }
        return count;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值