题目推荐来源:GitHub - LeetCode 101: A LeetCode Grinding Guide (C++ Version)
-
算法解释:贪心算法或贪心思想采用贪心的策略,保证每次操作都是局部最优的,从而使最后得到的结果是全局最优的。
-
Leetcode-455.分发饼干 - easy
class Solution {
public int findContentChildren(int[] g, int[] s) {
Arrays.sort(g);
Arrays.sort(s);
int child = 0, cookie = 0;
while (child < g.length && cookie < s.length) {
if(g[child] <= s[cookie]) ++child;
++cookie;
}
return child;
}
}
- Leetcode-135. 分发糖果 - hard
//这是最笨的直接模拟的方法
class Solution {
public int candy(int[] ratings) {
int index = 0;
int[] candies = new int[ratings.length];
while (index < ratings.length) {
if (index == 0) {
candies[index] = 1;
}else if (ratings[index-1] < ratings[index]) {
candies[index] = candies[index-1] + 1;
}else if (ratings[index-1] == ratings[index]) {
candies[index] = 1;
}else if (ratings[index-1] > ratings[index]) {
candies[index] = 1;
if(candies[index-1] == 1) {
candies[index-1] += 1;
int tIndex = index - 1;
while(tIndex > 0) {
if (ratings[tIndex-1] > ratings[tIndex] && candies[tIndex-1] <= candies[tIndex]) {
candies[tIndex-1] += 1;
--tIndex;
}else {
break;
}
}
}
}
++index;
}
int sum = 0;
for(int num : candies) {
sum += num;
}
return sum;
}
}
//这是使用贪心的很有趣的方法
class Solution {
public int candy(int[] ratings) {
int[] candies = new int[ratings.length];
candies[0] = 1;
for(int i = 1; i < ratings.length; ++i) {
if(ratings[i-1] < ratings[i]) {
candies[i] = candies[i-1] + 1;
}else {
candies[i] = 1;
}
}
for(int i = ratings.length - 1; i > 0; --i) {
if(ratings[i-1] > ratings[i] && candies[i-1] <= candies[i]) {
candies[i-1] = candies[i] + 1;
}
}
return Arrays.stream(candies).sum();
}
}
- Leetcode-435. 无重叠区间 - medium
class Solution {
public int eraseOverlapIntervals(int[][] intervals) {
Arrays.sort(intervals, new Comparator<int[]>() {
@Override
public int compare(int[] t1, int[] t2) {
return t1[1] - t2[1];
}
});
int remove = 0;
int last = 0;
for(int i = 1; i < intervals.length; ++i) {
if(intervals[i][0] >= intervals[last][1]){
last = i;
}else{
++remove;
}
}
return remove;
}
}
- Leetcode-605. 种花问题 - easy
//最笨的模拟方法,但是运行挺快,就是写起来比较繁琐
class Solution {
public boolean canPlaceFlowers(int[] flowerbed, int n) {
for(int i = 0; i < flowerbed.length && n != 0; ++i) {
if(flowerbed[i] != 0) continue;
if(i == 0) {
if(flowerbed.length == 1 || flowerbed[i+1] == 0){
--n;
flowerbed[i] = 1;
};
}else if (i == flowerbed.length-1) {
if(flowerbed[i-1] == 0) {
flowerbed[i] = 1;
--n;
}
}else {
if(flowerbed[i-1] == 0 && flowerbed[i+1] == 0) {
flowerbed[i] = 1;
--n;
}
}
}
return n == 0;
}
}
//数学归纳法,可归纳出连续0的数量与最大可种植数目的关系:(count-1)/2
//对于边界问题可假想为左右两边各补了一个0
public static boolean canPlaceFlowers(int[] flowerbed, int n) {
if (flowerbed == null || flowerbed.length == 0) return n == 0;
int countOfZero = 1; // 当前全0区段中连续0的数量,刚开始预设1个0,因为开头花坛的最左边没有花,可以认为存在一个虚无的0
int canPlace = 0; // 可以种的花的数量
for (int bed : flowerbed) {
if (bed == 0) { // 遇到0,连续0的数量+1
countOfZero++;
} else { // 遇到1,结算上一段连续的0区间,看能种下几盆花:(countOfZero-1)/2
canPlace += (countOfZero-1)/2;
if (canPlace >= n) return true;
countOfZero = 0; // 0的数量清零,开始统计下一个全0分区
}
}
// 最后一段0区还未结算:
countOfZero++; // 最后再预设1个0,因为最后花坛的最右边没有花,可以认为存在一个虚无的0
canPlace += (countOfZero-1)/2;
return canPlace >= n;
}
-- from Leetcode 官方题解下的评论 思路很不错 学习一下
- Leetcode-452. 用最少数量的箭引爆气球 - medium
//此处贪心的策略是要使得一只箭射爆最多的气球,及找到最多的公共区间。
//排序+贪心的思想
class Solution {
public int findMinArrowShots(int[][] points) {
if(points == null || points.length == 0) return 0;
Arrays.sort(points, Comparator.comparingInt(p -> p[1]));
int arrows = 1;
int end = points[0][1];
for(int[] point : points) {
if(point[0] > end) {
++arrows;
end = point[1];
}
}
return arrows;
}
}
- Leetcode-763. 划分字母区间 - medium
//自己按照贪心算法的思想写的代码
//1.首先统计每个字母出现的区间
//2.排序字母出现区间,按照先后顺序
//3.寻找最小的无重叠的组合区间(贪心的思想)
class Solution {
public List<Integer> partitionLabels(String S) {
Map<Character, int[]> pos = new HashMap<>(26);
for (int i = 0; i < S.length(); ++i) {
if (pos.containsKey(S.charAt(i))) {
pos.get(S.charAt(i))[1] = i;
} else {
pos.put(S.charAt(i), new int[]{i, i});
}
}
List<int[]> posList = new ArrayList<>(pos.values());
posList.sort(Comparator.comparingInt(t -> t[0]));
List<Integer> result = new ArrayList<>();
int start = -1;
int end = -1;
for (int[] entry : posList) {
if (entry[0] > end) {
if (start != -1) {
result.add(end - start + 1);
}
start = entry[0];
end = entry[1];
} else {
end = Math.max(end, entry[1]);
}
}
result.add(end - start + 1);
return result;
}
}
//官方题解
//使用数组下标对应字母序作为索引统计字母出现的最后位置
//遍历字符串统计当前出现字符的最后位置,直到达到当前片段的最后位置
//有时候你可能只差一步:学会深入思考。
class Solution {
public List<Integer> partitionLabels(String S) {
int[] last = new int[26];
int length = S.length();
for (int i = 0; i < length; i++) {
last[S.charAt(i) - 'a'] = i;
}
List<Integer> partition = new ArrayList<Integer>();
int start = 0, end = 0;
for (int i = 0; i < length; i++) {
end = Math.max(end, last[S.charAt(i) - 'a']);
if (i == end) {
partition.add(end - start + 1);
start = end + 1;
}
}
return partition;
}
}
- Leetcode-122. 买卖股票的最佳时机 II - easy
//自己思考的贪心算法,可以类比与函数图像的极值点
//当前的极大值减去之前的极小值可以得到这段时间的最大利润
class Solution {
public int maxProfit(int[] prices) {
int min = Integer.MAX_VALUE;
int profit = 0;
for (int i = 0; i < prices.length; ++i) {
if(prices[i] < min) {
min = prices[i];
}
else if(i == prices.length-1) {
if(prices[i] > min) {
profit += (prices[i] - min);
}
}
else if(prices[i] > prices[i-1] && prices[i] >= prices[i+1]) {
profit += (prices[i] - min);
min = prices[i+1];
}
System.out.println(i);
}
return profit;
}
}
//官方的贪心算法,很精辟
class Solution {
public int maxProfit(int[] prices) {
int ans = 0;
int n = prices.length;
for (int i = 1; i < n; ++i) {
ans += Math.max(0, prices[i] - prices[i - 1]);
}
return ans;
}
}
官方题解截图如下:来源力扣(LeetCode)
著作权归作者所有
- Leetcode-406. 根据身高重建队列 - Medium
class Solution {
public int[][] reconstructQueue(int[][] people) {
if (people == null || people.length == 0 || people.length == 1) {
return people;
}
Arrays.sort(people, (p1, p2) -> p1[0] == p2[0] ? (p2[1] - p1[1]) : p1[0] - p2[0]);
int[][] ans = new int[people.length][];
for (int[] p : people) {
int space = p[1] + 1;
int i = 0;
for (; i < ans.length; ++i) {
if (ans[i] == null) {
--space;
if (space == 0) {
break;
}
}
}
ans[i] = p;
}
return ans;
}
}
- Leetcode-665. 非递减数列 - Easy
//借鉴网友的贪心思想的算法
//这道题目看似是Easy的难度,但是贪心的策略还是挺难思考的
class Solution {
public boolean checkPossibility(int[] nums) {
int cnt = 0;
for (int i = 0; i < nums.length - 1 && cnt < 2; ++i) {
if (nums[i] > nums[i+1]) {
++cnt;
if(i > 0 && nums[i-1] > nums[i+1]) {
nums[i + 1] = nums[i];
} else {
nums[i] = nums[i + 1];
}
}
}
return cnt < 2;
}
}
贪心的策略:
- 要保证是一个非递减序列,即保证所有的nums[i] <= nums[i+1]
- 当出现一个逆序对时,即nums[i] > nums[i+1],此时要改变二者其一使得序列仍然保持非递减
- 如果改变 nums[i+1] (增大),可能会影响 i+1 之后的序列
- 如果改变 nums[i] (减小),可以肯定的是 nums[i] >= nums[i-1],那么将nums[i] 减小到与 nums[i-1] 相等即可。
- 那么综上这两点,贪心的策略即在发现逆序对时尽可能的减小 nums[i];
- 那在什么情况下需要增大nums[i+1]呢?
- 我们已知 nums[i] >= nums[i-1],nums[i] > nums[i+1],如果此时nums[i+1] < nums[i-1],那我们减小 nums[i] 到达 nums[i-1] 时仍然存在逆序 nums[i] > nums[i-1],所以在这种情况下我们应当增加 nums[i+1],使得nums[i+1] = nums[i];