贪心算法
一、定义
贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解。
贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关。
贪心策略适用的前提是:局部最优策略能导致产生全局最优解。
二、LeetCode相关题目
1、Leetcode_455. 分发饼干
题目描述:
假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。对每个孩子 i ,都有一个胃口值 gi ,这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j ,都有一个尺寸 sj 。如果 sj >= gi ,我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。
注意:
你可以假设胃口值为正。
一个小朋友最多只能拥有一块饼干。
示例 1:
输入: [1,2,3], [1,1]
输出: 1
解释:
你有三个孩子和两块小饼干,3个孩子的胃口值分别是:1,2,3。
虽然你有两块小饼干,由于他们的尺寸都是1,你只能让胃口值是1的孩子满足。
所以你应该输出1。
示例 2:
输入: [1,2], [1,2,3]
输出: 2
解释:
你有两个孩子和三块小饼干,2个孩子的胃口值分别是1,2。
你拥有的饼干数量和尺寸都足以让所有孩子满足。
所以你应该输出2.
题解:
class Solution {
public int findContentChildren(int[] g, int[] s) {
// 首先对两个数组排序
Arrays.sort(g);
Arrays.sort(s);
// 两个数组的下标索引
int gi = g.length - 1;
int si = s.length - 1;
// 满足孩子的数量
int result = 0;
// 分发饼干
while (gi >= 0 && si >= 0) {
// 将最大的饼干分给胃口最大的小孩 孩子满足
if (g[gi] <= s[si]) {
// 满足孩子数量+1
result++;
// 孩子胃口数下标-1
gi--;
// 饼干数组下标-1
si--;
} else {
// 将最大的饼干分给胃口最大的小孩 孩子不满足
// 将饼干分给胃口小一号的孩子
gi--;
}
}
return result;
}
}
2、Leetcode_392. 判断子序列
题目描述:
给定字符串 s 和 t ,判断 s 是否为 t 的子序列。
你可以认为 s 和 t 中仅包含英文小写字母。字符串 t 可能会很长(长度 ~= 500,000),而 s 是个短字符串(长度 <=100)。
字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。
示例 1:
s = “abc”, t = “ahbgdc”
返回 true.
示例 2:
s = “axc”, t = “ahbgdc”
返回 false.
后续挑战 :
如果有大量输入的 S,称作S1, S2, … , Sk 其中 k >= 10亿,你需要依次检查它们是否为 T 的子序列。在这种情况下,你会怎样改变代码?
致谢:
特别感谢 @pbrother 添加此问题并且创建所有测试用例。
题解:
class Solution {
public boolean isSubsequence(String s, String t) {
// 起始索引
int index = -1;
for (char c : s.toCharArray()) {
// 查询字符在不在字符串t中
index = t.indexOf(c, index + 1);
// 查询不到
if (index == -1) {
return false;
}
}
return true;
}
}
3、Leetcode_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) {
// 特殊情况处理
if (intervals.length == 0) {
return 0;
}
// 先排序 以区间末端从小到大排序
Arrays.sort(intervals, new Comparator() {
@Override
public int compare(int[] o1, int[] o2) {
return o1[1] - o2[1];
}
});
// 最多能组成的不重叠区间个数
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];
// 不重叠区间数+1
count++;
}
// 总区间数 - 不重叠区间数 = 要删去的区间数
return intervals.length - count;
}
}
4、Leetcode_665. 非递减数列
题目描述:
给定一个长度为 n 的整数数组,你的任务是判断在最多改变 1 个元素的情况下,该数组能否变成一个非递减数列。
我们是这样定义一个非递减数列的: 对于数组中所有的i (1 <= i < n),满足 array[i] <= array[i + 1]。
示例 1:
输入: [4,2,3]
输出: True
解释: 你可以通过把第一个4变成1来使得它成为一个非递减数列。
示例 2:
输入: [4,2,1]
输出: False
解释: 你不能在只改变一个元素的情况下将其变为非递减数列。
说明: n 的范围为 [1, 10,000]。
题解:
class Solution {
public boolean checkPossibility(int[] nums) {
int length = nums.length;
int count = 0;
for (int i = 1; i < length && count < 2; i++) {
if (nums[i] >= nums[i - 1]) {
continue;
}
count++;
if (i >= 2 && nums[i - 2] > nums[i]) {
// 是当前数字等于前一个数字
nums[i] = nums[i - 1];
} else {
// 使前一个数字小于当前数字
nums[i - 1] = Integer.MIN_VALUE;
}
}
return count <= 1;
}
}
5、Leetcode_1046. 最后一块石头的重量
题目描述:
有一堆石头,每块石头的重量都是正整数。
每一回合,从中选出两块最重的石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:
如果x == y,那么两块石头都会被完全粉碎;
如果x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为y-x。
最后,最多只会剩下一块石头。返回此石头的重量。如果没有石头剩下,就返回 0。
提示:
1 <= stones.length <= 30
1 <= stones[i] <= 1000
解答:
class Solution1406 {
public int lastStoneWeight(int[] stones) {
// 末尾下标
int end = stones.length - 1;
// 已处理的石头数量
int count = 0;
while (count != stones.length && count != end) {
// 先排序
Arrays.sort(stones);
int x = stones[end - 1];
int y = stones[end];
//x == y,那么两块石头都会被完全粉碎;
if (x == y) {
// 粉碎的石头标记为-1
stones[end - 1] = -1;
stones[end] = -1;
// 已处理的石头+2
count += 2;
} else {
stones[end - 1] = stones[end] - stones[end - 1];
// 粉碎的石头标记为-1
stones[end] = -1;
// 已处理的石头+2
count += 1;
}
}
Arrays.sort(stones);
// count == stones.length 表示全部处理完返回0 否则剩下一个未处理 返回stones[end]
return count == stones.length ? 0 : stones[end];
}
}