贪心算法
1. 思想
保证每次操作都是局部最优的,并且最后得到的结果是全局最优的。
什么是贪心算法呢?贪心算法可以认为是动态规划算法的一个特例,相比动态规划,使用贪心算法需要满足更多的条件(贪心选择性质),但是效率比动态规划要高。
比如说一个算法问题使用暴力解法需要指数级时间,如果能使用动态规划消除重叠子问题,就可以降到多项式级别的时间,如果满足贪心选择性质,那么可以进一步降低时间复杂度,达到线性级别的。
什么是贪心选择性质呢,简单说就是:每一步都做出一个局部最优的选择,最终的结果就是全局最优。注意哦,这是一种特殊性质,其实只有一小部分问题拥有这个性质。
动态规划算法通常以自底向上的方式解各子问题,而贪心算法则通常以自顶向下的方式进行,以迭代的方式作出相继的贪心选择,每作一次贪心选择就将所求问题简化为规模更小的子问题。
2. 经典问题
LeetCode题目
1. 分配饼干
难度简单293
假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。
对每个孩子 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。
关键:
把胃口值和饼干升序排
贪心思想是: 尽量用小饼干来满足小胃口的人
使用双指针,看最后还是是不是孩子都满足了
代码
class Solution {
public int findContentChildren(int[] g, int[] s) {
Arrays.sort(g);
Arrays.sort(s);
int g1 = 0;
int s1 = 0;
while(g1<g.length && s1<s.length){
if(g[g1] <= s[s1]){
g1++;
s1++;
} else {
s1++;
}
}
return g1;
}
}
2. 无重叠区间
给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。
注意:
- 可以认为区间的终点总是大于它的起点。
- 区间 [1,2] 和 [2,3] 的边界相互“接触”,但没有相互重叠。
示例 1:
输入: [ [1,2], [2,3], [3,4], [1,3] ]
输出: 1
解释: 移除 [1,3] 后,剩下的区间没有重叠。
关键:
这道题的本质就是 找出有几个区间是不重复的,得到不重叠的区间数量,在用总的数量减去不重叠的区间数量,就是多的区间的数量
重叠区间:按照end升序排
代码
class Solution {
public int eraseOverlapIntervals(int[][] intervals) {
if(intervals.length == 0) return 0;
// 找出互不重叠的区间, 然后就是len - k即可
Arrays.sort(intervals, new Comparator<int[]>(){
public int compare(int[] a, int[] b){
return a[1] - b[1]; // 以结束时间升序
}
});
int end = intervals[0][1];
int count = 1;
for(int i = 1; i<intervals.length;i++){
int[] interval = intervals[i];
if(interval[0]>=end){
count++;
end = interval[1];
}
}
return intervals.length-count;
}
}
3. 用最少数量的箭引爆气球
在二维空间中有许多球形的气球。对于每个气球,提供的输入是水平方向上,气球直径的开始和结束坐标。由于它是水平的,所以纵坐标并不重要,因此只要知道开始和结束的横坐标就足够了。开始坐标总是小于结束坐标。
一支弓箭可以沿着 x 轴从不同点完全垂直地射出。在坐标 x 处射出一支箭,若有一个气球的直径的开始和结束坐标为 x``start
,x``end
, 且满足 xstart ≤ x ≤ x``end
,则该气球会被引爆。可以射出的弓箭的数量没有限制。 弓箭一旦被射出之后,可以无限地前进。我们想找到使得所有气球全部被引爆,所需的弓箭的最小数量。
给你一个数组 points
,其中 points [i] = [xstart,xend]
,返回引爆所有气球所必须射出的最小弓箭数。
示例 1:
输入:points = [[10,16],[2,8],[1,6],[7,12]]
输出:2
解释:对于该样例,x = 6 可以射爆 [2,8],[1,6] 两个气球,以及 x = 11 射爆另外两个气球
关键:
这个就是和上一题完全一样的,没什么大的区别
class Solution {
public int findMinArrowShots(int[][] points) {
if(points.length == 0) return 0;
Arrays.sort(points, new Comparator<int[]>(){
public int compare(int[] a, int[] b){
return a[1] > b[1]? 1:-1; // 防止内存溢出!!!!!
}
});
int end = points[0][1];
int count = 1;
for(int i = 0; i<points.length;i++){
if(end<points[i][0]){
count++;
end = points[i][1];
}
}
return count;
}
}
贪心算法
1. 思想
保证每次操作都是局部最优的,并且最后得到的结果是全局最优的。
什么是贪心算法呢?贪心算法可以认为是动态规划算法的一个特例,相比动态规划,使用贪心算法需要满足更多的条件(贪心选择性质),但是效率比动态规划要高。
比如说一个算法问题使用暴力解法需要指数级时间,如果能使用动态规划消除重叠子问题,就可以降到多项式级别的时间,如果满足贪心选择性质,那么可以进一步降低时间复杂度,达到线性级别的。
什么是贪心选择性质呢,简单说就是:每一步都做出一个局部最优的选择,最终的结果就是全局最优。注意哦,这是一种特殊性质,其实只有一小部分问题拥有这个性质。
动态规划算法通常以自底向上的方式解各子问题,而贪心算法则通常以自顶向下的方式进行,以迭代的方式作出相继的贪心选择,每作一次贪心选择就将所求问题简化为规模更小的子问题。
2. 经典问题
LeetCode题目
1. 分配饼干
难度简单293
假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。
对每个孩子 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。
关键:
把胃口值和饼干升序排
贪心思想是: 尽量用小饼干来满足小胃口的人
使用双指针,看最后还是是不是孩子都满足了
代码
class Solution {
public int findContentChildren(int[] g, int[] s) {
Arrays.sort(g);
Arrays.sort(s);
int g1 = 0;
int s1 = 0;
while(g1<g.length && s1<s.length){
if(g[g1] <= s[s1]){
g1++;
s1++;
} else {
s1++;
}
}
return g1;
}
}
2. 无重叠区间
给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。
注意:
- 可以认为区间的终点总是大于它的起点。
- 区间 [1,2] 和 [2,3] 的边界相互“接触”,但没有相互重叠。
示例 1:
输入: [ [1,2], [2,3], [3,4], [1,3] ]
输出: 1
解释: 移除 [1,3] 后,剩下的区间没有重叠。
关键:
这道题的本质就是 找出有几个区间是不重复的,得到不重叠的区间数量,在用总的数量减去不重叠的区间数量,就是多的区间的数量
重叠区间:按照end升序排
代码
class Solution {
public int eraseOverlapIntervals(int[][] intervals) {
if(intervals.length == 0) return 0;
// 找出互不重叠的区间, 然后就是len - k即可
Arrays.sort(intervals, new Comparator<int[]>(){
public int compare(int[] a, int[] b){
return a[1] - b[1]; // 以结束时间升序
}
});
int end = intervals[0][1];
int count = 1;
for(int i = 1; i<intervals.length;i++){
int[] interval = intervals[i];
if(interval[0]>=end){
count++;
end = interval[1];
}
}
return intervals.length-count;
}
}
3. 用最少数量的箭引爆气球
在二维空间中有许多球形的气球。对于每个气球,提供的输入是水平方向上,气球直径的开始和结束坐标。由于它是水平的,所以纵坐标并不重要,因此只要知道开始和结束的横坐标就足够了。开始坐标总是小于结束坐标。
一支弓箭可以沿着 x 轴从不同点完全垂直地射出。在坐标 x 处射出一支箭,若有一个气球的直径的开始和结束坐标为 x``start
,x``end
, 且满足 xstart ≤ x ≤ x``end
,则该气球会被引爆。可以射出的弓箭的数量没有限制。 弓箭一旦被射出之后,可以无限地前进。我们想找到使得所有气球全部被引爆,所需的弓箭的最小数量。
给你一个数组 points
,其中 points [i] = [xstart,xend]
,返回引爆所有气球所必须射出的最小弓箭数。
示例 1:
输入:points = [[10,16],[2,8],[1,6],[7,12]]
输出:2
解释:对于该样例,x = 6 可以射爆 [2,8],[1,6] 两个气球,以及 x = 11 射爆另外两个气球
关键:
这个就是和上一题完全一样的,没什么大的区别
class Solution {
public int findMinArrowShots(int[][] points) {
if(points.length == 0) return 0;
Arrays.sort(points, new Comparator<int[]>(){
public int compare(int[] a, int[] b){
return a[1] > b[1]? 1:-1; // 防止内存溢出!!!!!
}
});
int end = points[0][1];
int count = 1;
for(int i = 0; i<points.length;i++){
if(end<points[i][0]){
count++;
end = points[i][1];
}
}
return count;
}
}