贪心算法:是指在对问题进行求解的时候,总是做出在当前看来最好的选择,即不易整体为考虑,而是局部最优解。
- 贪心算法并不能对所有问题都得出整体最优解,关键是贪心策略的选择。
- 无后效性:某个状态以前的过程不会影响以后的状态,只与当前状态有关。
- 一般能用贪心算法解决的问题是:在有一个限定值的情况下,使某个期望值能达到最高或者最低。
- 无需尝试去证明贪心算法的正确性,贪心策略一般是显而易见的。
贪心是一种思想,要参透它,还是要以刷题为主。
1、买卖股票的最佳时机(简单)-122
这道题的给出了连续好多天一直股票的价格,让我们能制订买卖策略获取最大收益,但是在现实中肯定是不行的。贪心的策略是不要放过任何一次涨,跌之前就卖掉(要真这样我就去炒股好了,我的手就可以拿来弹钢琴了)。
class Solution {
public int maxProfit(int[] prices) {
int profit = 0;
for(int i = 1; i < prices.length; i++) {
if(prices[i] > prices[i - 1]) {
profit += prices[i] - prices[i - 1];
}
}
return profit;
}
}
2、跳跃游戏(中等)-55
这道题说实话我没做出来,我一开始想的是从0开始每次我都跳最大,实在不行我退回来,但是无法通过,后来想想这样做,当跳一跃的时候,必定会影响到后面的一跃。
其实思想应该是:要跳跃到最后一个位置,中间的每个位置一定都能到达,如果把能到达终点的点成为好坐标,就可以从倒数第二个位置开始向前遍历,每个点要能到达终点,必须能够到达离他最近的好坐标,而后它就成了前面点的最近好坐标。最后只要判断0的位置是不是好坐标,就能解决问题了。
class Solution {
public boolean canJump(int[] nums) {
int len = nums.length;
int lastPos = len - 1;
for(int i = len - 1; i >= 0; i--) {
if(i + nums[i] >= lastPos) {
lastPos = i;
}
}
if(lastPos == 0) {
return true;
} else {
return false;
}
}
}
3、加油站 -134
一开始我是想从开到下个加油站能剩余最多油的站开始走,但是这样是会出现到了某段路程开不过的情况。我又想,那就不要错过每一个增量,也是行不通的。
其实,因为答案是唯一的,想走完路程,只要总油量比总油耗多,肯定是能走完的。从第一个油站开始,若出现油耗不够,那么起点必须在这个加油站的后面,只要某个点能走到最后一个站,那它必须能走一圈。
class Solution {
public int canCompleteCircuit(int[] gas, int[] cost) {
int start = 0;
int total = 0;
int sum = 0;
for(int i = 0; i < gas.length; i++) {
int rest = gas[i] - cost[i];
total += rest;
sum += rest;
if(sum < 0) {
start = i + 1;
sum = 0;
}
}
return total >= 0 ? start : -1;
}
}
4、判断子序列 -392
一开始没理解轻触题目,哦,这不是洒洒水,我用个26长度的数组统计一下,你来10亿个我也能判断,后来才发现忽略了顺序。还是得乖乖地去遍历字符串,但是比对了别人的答案,相形见绌,别人都是用字符串的方法,而我对api都不熟悉,方法实在不高明啊。力扣归类的是贪心,我没悟到。
我的:
class Solution {
public boolean isSubsequence(String s, String t) {
if("".equals(s)) {
return true;
}
if("".equals(t)) {
return false;
}
char[] a = s.toCharArray();
char[] b = t.toCharArray();
int j = 0;
int len = a.length;
for(int i = 0; i < b.length; i++) {
if(b[i] == a[j]) {
j++;
if(j == len) {
return true;
}
}
}
return false;
}
}
别人的:
class Solution {
public boolean isSubsequence(String s, String t) {
int i = -1;
for(char c : s.toCharArray()){
i = t.indexOf(c,i+1);
System.out.println(i);
if(i == -1){
return false;
}
}
return true;
}
}
作者:shu-pi-2
链接:https://dev.lingkou.xyz/problems/two-sum/solution/chong-fen-li-yong-indexofmark-by-shu-pi-2/
来源:力扣(LeetCode)
5、分发糖果 -135
之前做的分糖果题目是孔融让梨一样小吃小大吃大来分大小不同的题目,所以一开始切入的时候我还是在找最低分的孩子,但是这道题每个孩子要比较的只是他相邻的孩子,所以解决不了。那么,一个孩子可能有两个相邻的孩子,所以既要跟左边的相比也要跟右边的相比,就会有两条规矩:最先给每个孩子都发上一个,先从左边开始遍历,只要我比我左边的人分数高,我就要分到比他的糖果,不然我就不开心,再从右边开始遍历,每个人与他右边的孩子相比(没有就不用),最终糖果数分别用两个数组存储,每个孩子都要满足这两个约束,那当然是取多的发。
class Solution {
public int candy(int[] ratings) {
int count = 0;
int n = ratings.length;
int[] l = new int[n];
int[] r = new int[n];
//初始时每个学生都分配1个糖果
for(int i = 0; i <= n - 1; i++) {
l[i] = 1;
r[i] = 1;
}
//从左边遍历得到分配数组
for(int i = 1; i <= n - 1; i++) {
if(ratings[i] > ratings[i - 1]) {
l[i] = l[i - 1] + 1;
}
}
//在从右边遍历得到分配数组
for(int i = n - 2; i >= 0; i--) {
if(ratings[i] > ratings[i + 1]) {
r[i] = r[i + 1] + 1;
}
}
for(int i = 0; i < n; i++) {
count += l[i] > r[i] ? l[i] : r[i];
}
return count;
}
}
6、按要求补齐数组 -330
欸,这道题就比较有难度了。一开始我想的居然是从n开始去减最大的数,然后再n-1继续,然后就没有然后了,怎么可能嘛,真是愚蠢的雷霆球迷。
这道题讲究的是一个覆盖,要覆盖[1, n]的区间,当然使用比n的数来相加,子区间也是同样的道理,所以从1开始,用一个miss来表示1开始的区间覆盖不到的第一个,如果miss>=某个元素nums[i]就证明区间连上了,覆盖不到的就是nums[i]+miss了,并检查下个元素nums[i+1];如果miss<nums,那肯定就要补充数了,贪心就体现在这里了,补充比miss大的数,那miss不能被覆盖到,补上比miss小的数,就有点吃亏了,那miss得变为多少呢?本来前面所有数加上最多是miss-1,又加上了miss,那miss就等于2miss了。
class Solution {
public int minPatches(int[] nums, int n) {
int patches = 0;
//miss找不到的时候是要翻倍的,用Int可能会发生溢出
long miss = 1;
int i = 0;
while(miss <= n) {
if(i < nums.length && miss >= nums[i]) {
//两个区间重叠,区间能覆盖到num[i]+miss-1,去一个区间从i+1开始
miss += nums[i++];
} else {
//补上miss这个数,加上前面的数刚好能覆盖到2miss-1
miss += miss;
patches ++;
}
}
return patches;
}
}
7、跳跃游戏Ⅱ -45
因为做过之前跳跃游戏那道题,在解这题的时候我还是用了相同的方法,就是由后往前来遍历,只要能到达终点的就是好坐标,然后遍历某个点能到达的所有好坐标,选出最短的跳数。
class Solution {
public int jump(int[] nums) {
int n = nums.length;
int[] steps = new int[n];
for(int i = 0; i < n; i++) {
steps[i] = -1;
}
steps[n - 1] = 0;
for(int i = n - 2; i >= 0; i--) {
int least = -1;
for(int j = i + nums[i]; j >= i; j--) {
if(j <= n - 1 && steps[j] != -1) {
//选取跳数最少的
int step = steps[j] + 1;
if(least == -1) {
least = step;
} else if(least > step) {
least = step;
}
}
}
steps[i] = least;
}
return steps[0];
}
}
很明显这个耗时是非常不理想的,这个解法还是不够高明。可以看到就是在代码中是嵌套了两层循环,时间复杂度会达到O(n*n)。
其实这个题目已经明确告诉我们是能到达终点的,所以我们从起点开始遍历,每次都跳到可到达点中能跳最远距离的点。
class Solution {
public int jump(int[] nums) {
int steps = 0;
int end = 0;
int maxPosition = 0;
for(int i = 0; i < nums.length - 1; i++) {
//maxPosition表示可达点能跳跃到的最远的距离
maxPosition = Math.max(maxPosition, nums[i] + i);
//end表示的是当前点一步可达的范围边界
if(i == end) {
end = maxPosition;
steps ++;
}
}
return steps;
}
}
这次就快很多了,因为只需要遍历一次数组,时间复杂度减低到O(n)。