Leetcode 贪心专题周 (内附C++及Java代码)

455.分发饼干

贪心算法:
问题:二分图最大匹配
涉及算法:匈牙利算法 时间复杂度是: O ( n × m ) O(n × m) O(n×m)

题意:
n n n 个小朋友 m m m 个饼干
g i g_i gi :小朋友希望得到饼干的最小尺寸,小朋友比较傲娇,如果没达到最小尺寸就不要
s i s_i si :每个饼干的尺寸

问最多能满足多少个小朋友的要求?

虽说是贪心算法,但是也是属于纯思维能解决的题目。
解决思路:应该满足更多的小朋友,所以我们应该选择 g i g_i gi 比较小的小朋友

g i , s i g_i,s_i gi,si 排序
② 只满足 g i g_i gi 最小的一段小朋友
③ 依次遍历,尽可能地压榨,在 数组s 中找到第一个 ≥ 数组g 中的第一个小朋友,再找对应的第二个。

全局的最优解,调整成该子集的最优解思想:{ s i s_i si 随着 g i g_i gi 单调}∈{只满足 g i g_i gi 最小的一段小朋友}∈{all}

C++代码

class Solution {
public:
    int findContentChildren(vector<int>& g, vector<int>& s) {
        int res = 0;
        sort(g.begin(), g.end());
        sort(s.begin(), s.end());
        int i = 0, j = 0;
        while (i < g.size() && j < s.size()) {
            if (s[j] >= g[i]) {
                i ++;
            }
            j++;
        }
        return i;
    }
};

Java代码

class Solution {
    public int findContentChildren(int[] g, int[] s) {
        int res = 0;
        Arrays.sort(g);
        Arrays.sort(s);
        int i = 0, j = 0;
        while (i < g.length && j < s.length) {
            if (s[j] >= g[i]) {
                i++;
            }
            j++;
        }
        return i;
    }
}

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

题意:弓箭与x轴垂直射出,射程无限,有一些气球在x轴上,给出气球宽度的区间,问:最少用多少支弓箭可以将全部气球引爆?

解题思想:这道题看似是二维的,实际上这个就是一维的问题。
可以把气球投影到x轴上,那么气球就变成了线段,只需要在这线段上取点,就可以引爆气球。

本题实际上就是贪心算法里面的区间选点问题。

则本题可转换问题为区间选点的模板问题:
给定N个闭区间 [ a i , b i ] [a_i,b_i] [ai,bi],请你在数轴上选择尽量少的点,使得每个区间内至少包含一个选出的点。输出选择的点的最小数量。(位于区间端点上的点也算作区间内)

题解:
将所有区间的右端点进行排序,下面将举例说明:
【1, 4】【2, 5】【3, 6】【5, 8】
则最多只需要2支箭便可引爆所有气球。
① 的右端点 在② 和 ③ 的区间内,所以①②③就可以一起引爆了。
④ (新区间)则要拿多一支箭。
那么如何确定 是不是新区间 呢?
只需要满足:新区间 ④ 的左端点 > 原区间 ① 的右端点

C++代码

class Solution {
    static bool cmp(const vector<int> &a, const vector<int> &b){
        return a[1] < b[1];
    }
public:
    //区间求交集
    //vector<>里面的vector存的是区间的初始坐标和结束坐标
    int findMinArrowShots(vector<vector<int>>& points) {
        if (points.size() == 0)
            return 0;
        //将区间从小到大排序
        sort(points.begin(), points.end(), cmp);
        int ans = 1, l = points[0][0], r = points[0][1];
        for (int i = 1; i < points.size(); i++) {
            //求交集
            //当遇到一个新区间,如果 r 小于新区间的起点,就需要一个新飞镖。
            //否则原飞镖的区间 l 和新区间的起点取最大值,r和新区间的终点取最小值
            if (r < points[i][0]) {
                l = points[i][0], r = points[i][1];
                ans++;
            } else {
                l = max(l, points[i][0]), r = min(r, points[i][1]);
            }
        }
        return ans;
    }
};

Java代码

//用来存区间端点
class Pair {
    int l, r;
    Pair(int l, int r) {
        this.l = l;
        this.r = r;
    }
}
class Solution {
    static int N = 10010;
    static Pair[] pair = new Pair[N];
    public int findMinArrowShots(int[][] points) {
        //区间选点问题
        if (points.length == 0) return 0;
        for (int i = 0; i < points.length; ++i) {
            int l = points[i][0], r = points[i][1];
            pair[i] = new Pair(l, r);
        }
        //按右端点排序
        Arrays.sort(pair, 0, points.length, (x, y) -> {
            if(x.r <= y.r)
                return -1;
            return 1;
        });
        //初始化
        int ans = 1;
        int end = pair[0].r;//初始化为第一个区间的右端点。
        for (int i = 1; i < points.length; ++i) {
            int l = pair[i].l;
            int r = pair[i].r;
            //如果新区间的左端点值 大于 原区间的右端点值
            if (l > end) {
                ans++;//选择一个新的区间
                end = r;//端点改为新区间的右端点
            }
        }
        return ans;
    }
}

376.摆动序列

贪心思想:
最优解 == 局部极值的数量 + 首尾两端点
证明:
如果在 [ A , D ] [A, D] [A,D] 之间,存在一个 B B B,满足: y ( B ) < y ( A ) y(B) < y(A) y(B)<y(A) y ( B ) < y ( D ) y(B) < y(D) y(B)<y(D),那么 [ A , D ] [A, D] [A,D] 之间一定存在一个极小值 —— y ( c ) y(c) y(c)
反之,存在极大值。

特殊的:需要特判,先去掉连续重复的数,不然就会:
假设输入案例为:0 0
预计输出为:1
但是实际上输出为:2
解决方案:用unique()和erase()去除连续相同的元素。

一张图说明一切:

时间复杂度: O ( n ) O(n) O(n)

C++代码

class Solution {
public:
    int wiggleMaxLength(vector<int>& nums) {
        //去掉连续相同的元素,unique()的返回值是去重之后的尾地址
        nums.erase(unique(nums.begin(), nums.end()), nums.end());
        if (nums.size() <= 2) return nums.size();
        //首尾两个元素是必须要的。
        int ans = 2;
        //时间复杂度:O(n)
        for (int i = 1; i < nums.size() - 1; ++i) {
        	//如果存在极值则ans++
            if (nums[i] > nums[i - 1] && nums[i] > nums[i + 1] || nums[i] < nums[i - 1] && nums[i] < nums[i + 1]) {
                ans++;
            }
        }
        return ans;
    }
};

316.去除重复字母

解法:
for example:
bcabc → abc 而不是bca → 保证字典序最小。
依次遍历原字符串:在这里插入图片描述

时间复杂度: O ( n ) O(n) O(n)
步骤:
① 用一个字符串记录了一个答案
② 从前往后枚举原字符串的每个字母。
③ 对于当前枚举的字母,这个字母答案串的最后一个字母进行比较如果小于答案串最后一个字母且答案最后一个字母可以删掉(也就是这个字母在后面还出现过),就删掉就好
④ 如果字母已经出现过则跳过即可。

C++代码

class Solution {
public:
    string removeDuplicateLetters(string s) {
        string stk;//存答案的
        unordered_map<char, bool> ins;//instack记录每个字母是不是在栈出现过。
        unordered_map<char, int> pos;//用于记录每个字母最后出现的位置。
        //记录每个字母最后出现的位置。
        for (int i = 0; i < s.size(); ++i) {
            pos[s[i]] = i;
        }
        //枚举
        for (int i = 0; i < s.size(); ++i) {
            //如果这个字符之前已经出现过则pass
            if (ins[s[i]]) continue;
            char ch = s[i];//记录当前字符
            //当满足:
            //1.答案字符串存在
            //2.字符串最后一个字母字典序是大于当前字母的
            //3.后面还有这个字母
            //就可以删掉当前字母
            while (stk.size() && stk.back() > s[i] && pos[stk.back()] > i) {
                ins[stk.back()] = false;//标记为在栈里不存在。
                stk.pop_back();//删掉
            }
            stk += s[i];//插到字符串后面
            ins[s[i]] = true;//标记该字符已在栈中出现过
        }
        return stk;
    }
};

Java代码

class Solution {
    public String removeDuplicateLetters(String s) {
        int[] cnt = new int[26]; // 统计每个字母出现次数
        boolean[] st = new boolean[26]; // 标记每个字母是否出现在答案中
        //先统计字符串中字母个数
        for (char ch : s.toCharArray()) {
            cnt[ch - 'a']++;
        }
        StringBuilder sb = new StringBuilder();
        for (char ch : s.toCharArray()) {
            cnt[ch - 'a']--;//该字母个数-1
            //如果这个字符之前已经出现过则pass
            if (st[ch - 'a']) continue;
            // 当前字母a小于前一个字母b并且b剩余个数大于0时,删除b
            // 因为ab的字典序是小于ba的

            //当满足:
            //1.答案字符串存在
            //2.字符串最后一个字母字典序是大于当前字母的
            //3.后面还有这个字母
            //就可以删掉当前字母
            while (sb.length() > 0
               && sb.charAt(sb.length() - 1) > ch && cnt[sb.charAt(sb.length() - 1) - 'a'] > 0) {
                //删掉当前字母
                st[sb.charAt(sb.length() - 1) - 'a'] = false;
                sb.deleteCharAt(sb.length() - 1);
            }
            sb.append(ch);//插到字符串后面
            st[ch - 'a'] = true;//标记该字符已出现过
        }
        return sb.toString();
    }
}

435.无重叠区间

这道题跟 leetcode 452.用最少数量的箭引爆气球 原理是一样的。
本题说的是无重叠区间,那么是:

在这里插入图片描述
区间选点问题:详情请看 上面👆👆 leetcode 452.用最少数量的箭引爆气球
详细的图文详解:见另一篇博客:(待更新)

C++代码

class Solution {
    static bool cmp(const vector<int> &a, const vector<int> &b){
        return a[1] < b[1];
    }
public:
    //区间求交集
    //vector<>里面的vector存的是区间的初始坐标和结束坐标
    int eraseOverlapIntervals(vector<vector<int>>& intervals) {
        if (intervals.size() == 0)
            return 0;
        //将区间从小到大排序
        sort(intervals.begin(), intervals.end(), cmp);
        int ans = 1, l = intervals[0][0], r = intervals[0][1];
        for (int i = 1; i < intervals.size(); i++) {
            //求交集
            //当遇到一个新区间,如果 新区间的起点 大于等于 原区间的右端点,就需要一个新区间。
            //否则原区间 l 和新区间的起点取最大值,r和新区间的终点取最小值
            if (intervals[i][0] >= r) {
                l = intervals[i][0], r = intervals[i][1];
                ans++;
            } else {
                l = max(l, intervals[i][0]), r = min(r, intervals[i][1]);
            }
        }
        return intervals.size() - ans;
    }
};

Java代码

//用来存区间端点
class Pair {
    int l, r;
    Pair(int l, int r) {
        this.l = l;
        this.r = r;
    }
}
class Solution {
    public int eraseOverlapIntervals(int[][] intervals) {
        int N = intervals.length;
        Pair[] pair = new Pair[N];
        //区间选点问题
        if (intervals.length == 0) return 0;
        for (int i = 0; i < intervals.length; ++i) {
            int l = intervals[i][0], r = intervals[i][1];
            pair[i] = new Pair(l, r);
        }
        //按右端点排序
        Arrays.sort(pair, 0, intervals.length, (x, y) -> {
            if(x.r <= y.r)
                return -1;
            return 1;
        });
        //初始化
        int ans = 1;
        int end = pair[0].r;//初始化为第一个区间的右端点。
        for (int i = 1; i < intervals.length; ++i) {
            int l = pair[i].l;
            int r = pair[i].r;
            //如果新区间的左端点值 大于等于 原区间的右端点值
            if (l >= end) {
                ans++;//选择一个新的区间
                end = r;//端点改为新区间的右端点
            }
        }
        return intervals.length - ans;
    }
}

406.根据身高重建队列

C++ Code

class Solution {
public:
    vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
        auto cmp = [](const vector<int> &a, const vector<int> &b) {
                return a[0] > b[0] || (a[0] == b[0] && a[1] < b[1]);
        };
        sort(people.begin(), people.end(), cmp);
        vector<vector<int>> res;
        for(auto p : people) 
            res.insert(res.begin() + p[1], p);
        return res;
    }
};

Code:Java

class Solution {
    public int[][] reconstructQueue(int[][] people) {
        Arrays.sort(people, (a, b) -> a[0] == b[0] ? a[1] - b[1] : b[0] - a[0]);
        List<int[]> list = new LinkedList<>();
        for (int[] p : people) {
            list.add(p[1], p);
        }
        return list.toArray(new int[people.length][2]);
    }
}
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值