【LeetCode】贪心算法:常见典例

贪心算法

如果问题的最优解包含两个(或更多)子问题的最优解,且子问题多有重叠,我们考虑使用动态规划算法。
而如果问题经过贪心选择后,只剩下
一个
子问题,且具有优化子结构,那么可以使用贪心算法。

贪心选择性:每一步贪心选出来的一定是原问题的最优解的一部分(即每次求的最优解一定会被更大的父问题选择,即被父节点选择)

  • 关键点就在于这个性质,就是说怎么证明父状态转移到的这唯一一个子状态就是父状态要使用的最优解

最优子结构:每一步贪心选完后会留下子问题,子问题的最优解和贪心选出来的解可以凑成原问题的最优解

贪心算法的实现框架

  • 贪心的实现也是自顶向下的,但是不用递归,因为子节点必须只有一个(即一个状态转移到子节点的选择函数里,每种情况的状态必须只有一个选择),所以循环就可以
定义解元素集合

// 从初始状态出发;
while (能朝给定总目标前进一步){ 
	利用可行的决策,求出可行解的一个解元素并加入解集合;
}

由所有解元素组合成问题的一个可行解;
  • 对比动态规划,动态规划是 for 循环的嵌套
  • 每一个贪心算法之下,几乎总有一个更加繁琐的动态规划算法。——CLRS

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(int[] g, int[] s) {
        Arrays.sort(g);
        Arrays.sort(s);

        int res = 0;
        for(int sIdx = s.length - 1 , gIdx = g.length - 1; sIdx != -1 && gIdx != -1 ; ){
            if(s[sIdx] >= g[gIdx]){
                res ++;
                sIdx --;
                gIdx --;
            }else {
                gIdx --;
            }
        }

        return res;
    }
}

392. 判断子序列

给定字符串 s 和 t ,判断 s 是否为 t 的子序列。

字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。

示例 1:

输入:s = "abc", t = "ahbgdc"
输出:true

示例 2:

输入:s = "axc", t = "ahbgdc"
输出:false

题解

class Solution {
    public boolean isSubsequence(String s, String t) {
        int sIdx = 0;
        for(int tIdx = 0 ; tIdx < t.length() ; tIdx ++){
            if(sIdx <= s.length() - 1 && s.charAt(sIdx) == t.charAt(tIdx)){
                sIdx ++;
            }
        }

        return sIdx == s.length();
    }
}

435. 无重叠区间

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

注意:

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

示例 1:

输入: [ [1,2], [2,3], [3,4], [1,3] ]

输出: 1

解释: 移除 [1,3] 后,剩下的区间没有重叠。

示例 2:

输入: [ [1,2], [1,2], [1,2] ]

输出: 2

解释: 你需要移除两个 [1,2] 来使剩下的区间没有重叠。

示例 3:

输入: [ [1,2], [2,3] ]

输出: 0

解释: 你不需要移除任何区间,因为它们已经是无重叠的了。

动态规划

class Solution {
    public int eraseOverlapIntervals(int[][] intervals) {
        Arrays.sort(intervals , (o1 , o2) -> (o1[0] != o2[0] ? o1[0] > o2[0] : o1[1] > o2[1]) ? 1 : -1);

        int[] dp = new int[intervals.length + 1];
        for(int i = intervals.length - 1 ; i >= 0 ; i --){
            for(int j = i + 1 ; j < intervals.length ; j ++){
                if(intervals[j][0] >= intervals[i][1]){
                    dp[i] = Math.max(dp[i] , dp[j] + 1);
                }
            }
            dp[i] = dp[i] == 0 ? 1 : dp[i];
        }

        int max = 0;
        for(int i = 0 ; i < dp.length ; i ++){
            max = Math.max(dp[i] , max);
        }

        return intervals.length - max;

    }
}

贪心算法

class Solution {
    public int eraseOverlapIntervals(int[][] intervals) {
        if (intervals.length == 0) {
            return 0;
        }
        // 注意,这里是根据结尾元素大小排序
        Arrays.sort(intervals,(o1 , o2) -> o1[1] > o2[1] ? 1 : o1[1] < o2[1] ? -1 : o1[0] > o2[0] ? 1 : o1[0] < o2[0] ? -1 : 0);
        int count = 1;	//最多能组成的不重叠区间个数
        int end = intervals[0][1];
        for (int i = 0; i < intervals.length; i++) {
            if (intervals[i][0] < end) {
                continue;
            }
            end = intervals[i][1];
            count++;
        }
        return intervals.length - count;
    }
}
上一篇:leetcode-56. 合并区间


下一篇:LeetCode_56.合并区间