贪心算法不一定能求得最优解,使用贪心的场景要求问题具有最优子结构。其实是比较抽象的。贪心算法的常见体现主要在图相关算法,堆排序等复杂算法,不再刷题的范围之内,我们做要掌握的贪心题目主要有区间问题和跳跃游戏问题。并通过刷题培养对贪心题目的感觉
leetcode455
分发饼干既要满足孩子的胃口,又要尽可能减少饼干的浪费,局部最优就是大的饼干优先分发给胃口大的孩子,全局最优就是喂饱更多的孩子
public int findContentChildren(int[] g, int[] s) {
Arrays.sort(g);
Arrays.sort(s);
int n = s.length-1;
int m = g.length-1;
int count = 0;
while(n>=0&&m>=0){
if(s[n]>=g[m]){
n--;
m--;
count++;
}else{
m--;
}
}
return count;
}
leetcode860
找零过程中,既要保证能够正确找零,又要减少小额零钱的使用,局部最优是先使用大额零钱进行找零,全剧最优就是能够进行更多次的找零。可以优先使用10元找零,然后再使用5元找零
public boolean lemonadeChange(int[] bills) {
int yuan5 = 0;
int yuan10 = 0;
for(int i=0;i<bills.length;i++){
if(bills[i]==5){
yuan5++;
}else if(bills[i]==10){
if(yuan5<=0){
return false;
}else{
yuan5--;
yuan10++;
}
}else{
if(yuan10>0&&yuan5>0){
yuan10--;
yuan5--;
}else if(yuan5>=3){
yuan5-=3;
}else{
return false;
}
}
}
return true;
}
leetcode135
可以首先设置给第一个孩子一个糖果,然后从左到右遍历得分数组,如果后一个孩子的得分大于前一个孩子,则所获的糖果数目加一,否则,获得的糖果数目设置为1,之后在从右到左便利得分数组,如果前一个孩子的得分大于后一个孩子,则取当前所获得的糖果数量和后一个孩子糖果数量加1的最大值
public int candy(int[] ratings) {
int[] candy = new int[ratings.length];
candy[0] = 1;
for(int i=1;i<candy.length;i++){
if(ratings[i]>ratings[i-1]){
candy[i] = candy[i-1]+1;
}else{
candy[i]=1;
}
}
for(int i=candy.length-2;i>=0;i--){
if(ratings[i]>ratings[i+1]){
candy[i] = Math.max(candy[i],candy[i+1]+1);
}
}
int res = 0;
for(int i:candy){
res+=i;
}
return res;
}
leetcode252
将区间按照开始时间进行排序,然后依次判断是否存在后一个区间的开始时间小于前一个区间的结束时间,如果存在,则区间重叠,否则区间不重叠
public boolean canAttendMeetings(int[][] intervals){
Arrays.sort(intervals,(a,b)->a[0]-b[0]);
for(int i=1;i<=intervals.length;i++){
if(intervals[i][0]<intervals[i-1][1]){
return false;
}
}
return true;
}
leetcode56
首先将区间按开始时间进行排序,然后将第一个区间加入结果集中,之后从第二个区间开始判断,判断当前区间和结果集中的最后一个区间是否重叠,不重叠则直接将当前区间加入结果集,重叠则进行合并,合并时,取两个区间的开始时间的较小者作为合并结果的开始时间,将两个区间的结束时间的较大者作为合并结果的结束时间
public int[][] merge(int[][] intervals) {
int[][] ans = new int[intervals.length][2];
Arrays.sort(intervals,(a,b)->(a[0]-b[0]));
ans[0] = intervals[0];
int i=1;
int j=0;
while(i<intervals.length){
if(intervals[i][0]>ans[j][1]){
j++;
ans[j] = intervals[i];
i++;
}else{
int start = Math.min(ans[j][0],intervals[i][0]);
int end = Math.max(ans[j][1],intervals[i][1]);
ans[j][0] = start;
ans[j][1] = end;
i++;
}
}
return Arrays.copyOf(ans,j+1);
}
leetcode57
首先要找到插入位置,即待插入区间的开始时间不大于区间序列中的某个区间的结束时间的区间,之后判断,如果待插入区间的结束时间小于当前区间的开始时间,则直接插入在当前区间之前,否则进行合并,合并时,取两个区间的开始时间的较小者作为合并结果的开始时间,将两个区间的结束时间的较大者作为合并结果的结束时间
public int[][] insert(int[][] intervals, int[] newInterval) {
if(intervals==null || intervals.length==0){
return new int[][]{{newInterval[0],newInterval[1]}};
}
int[][] res = new int[intervals.length+1][2];
int i = 0;
int j = 0;
while(i<intervals.length && newInterval[0]> intervals[i][1]){
res[j++] = intervals[i++];
}
if(i>=intervals.length){
res[j] = newInterval;
return res;
}
if(newInterval[1]< intervals[i][0]){
res[j] = newInterval;
}else{
int start = Math.min(newInterval[0],intervals[i][0]);
int end = Math.max(newInterval[1],intervals[i][1]);
res[j] = new int[]{start,end};
i++;
}
while (i<intervals.length){
if(intervals[i][0]<=res[j][1]){
int start = Math.min(res[j][0],intervals[i][0]);
int end = Math.max(res[j][1],intervals[i][1]);
res[j] = new int[]{start,end};
i++;
}else{
j++;
res[j] = intervals[i];
i++;
}
}
return Arrays.copyOf(res,j+1);
}
leetcode763
首先,便利字符串,找到每个字符在字符数组中最后出现的下标并记录,之后,再次遍历,如果记录当前遍历到的字母的最后出现下标并不断更新,如果当前遍历到的字母的下标和最后出现的下标相同,则找到分割点
public List<Integer> partitionLabels(String s) {
List<Integer> res = new ArrayList<>();
int[] last = new int[26];
for(int i = 0; i < s.length(); i++){
char c = s.charAt(i);
last[c-'a'] = i;
}
int start = 0;
int end = 0;
for (int i = 0; i < s.length(); i++){
end = Math.max(end, last[s.charAt(i) - 'a']);
if(end==i){
res.add(end-start+1);
start = end+1;
}
}
return res;
}
leetcode134
首先从1站出发,记录当前车站加油量和到下一车站耗油量之差,如果差大于零,继续出加油出发并计算,同时记录总的加油量与耗油量之差。如果差小于0,从下一站重新出发,并设置当前记录为0,便利结束后,如果总油量小于0,则无法返回,否则返回起始位置
public int canCompleteCircuit(int[] gas, int[] cost) {
int currentSum = 0;
int totalSum = 0;
int start = 0;
for (int i = 0; i < gas.length; i++) {
currentSum += gas[i] - cost[i];
totalSum += gas[i] - cost[i];
if (currentSum < 0) {
start = i + 1;
currentSum = 0;
}
}
if (totalSum<0){
return -1;
}
return start;
}
leetcode55
我们不应该考虑具体跳到哪个位置,而是应该考虑跳到当前位置后所能覆盖的最大范围,并考虑当前位置和最大覆盖范围位置之间所有位置的最大覆盖范围,不断更新最大覆盖范围,如果最大覆盖范围包括或者超过数组的最后一个元素,则可以到达,否则不可以到达。
public boolean canJump(int[] nums) {
int cover = 0;
for(int i=0;i<=cover;i++){
cover = Math.max(cover,i+nums[i]);
if(cover>=nums.length-1){
return true;
}
}
return false;
}
leetcode45
可以使用贪心+双指针的方式实现。同样的,我们需要考虑跳到当前位置后所能覆盖的最大范围,并考虑当前位置和最大覆盖范围位置之间所有位置的最大覆盖范围。设置四个变量,left用来遍历数组,right,当前位置所能到的边界,steps记录到达边界所需要的步数,max记录left到right过程中的最大覆盖范围,当left与right相遇时,更新right和steps
public int jump(int[] nums) {
if(nums.length==1){
return 0;
}
int right = 0;
int steps = 0;
int max = 0;
for (int left = 0; left < nums.length; left++) {
max = Math.max(max, left + nums[left]);
if(left==right){
steps++;
right = max;
}
if(right>=nums.length-1){
return steps;
}
}
return steps;
}