代码随想录-贪心算法【1】(Java)

分发饼干

链接: 455.分发饼干

思路

用大饼干满足胃口尽可能大的小孩

对饼干和胃口两个数组排序之后,选择大饼干,因为是胃口尽可能大,所以我们从大到小遍历胃口,直到遍历到符合条件的大胃口。count++后,我们的start–,到了次大的饼干,接着刚才的遍历继续遍历。

代码

class Solution {
    public int findContentChildren(int[] g, int[] s) {
        if (s.length == 0)  return s.length;
        Arrays.sort(g);
        Arrays.sort(s);
        int count = 0;
        int start = s.length - 1;
        for (int i = g.length - 1;i >= 0;i--){
            if (start >= 0 && g[i] <= s[start]){
                count++;
                start--;
            }
        }
        return count;
    }
}

摆动序列

链接: 376. 摆动序列

思路

贪心算法就是把思路简单化,因为是求摆动序列的长度,因此我们不必将代码里面不符合条件的元素删除,只需要忽略他们即可。

把序列按照大小画成有高低起伏的样子,需要重点考虑以下三种情况:

考虑三种情况:

  1. 上下有平坡
  2. 首尾元素
  3. 单调有平坡

上下有平坡:

if ((curDiff > 0 && preDiff <= 0) || (curDiff < 0 && preDiff >= 0))

上下变化即可

首尾元素的话,主要是考虑到,“仅有一个元素或者含两个不等元素的序列也视作摆动序列。”条件

单独一个元素,我们将之单独写出来

两个不等元素,我们可以假设第一个元素前还有一个平坡,preDiff=0,count=1

单调有平坡:

for (int i = 1;i < nums.length;i++){
             curDiff = nums[i] - nums[i-1];
             if ((curDiff > 0 && preDiff <= 0) || (curDiff < 0 && preDiff >= 0)){
                 count++;
                 preDiff = curDiff;
             }
        }

将preDiff = curDiff;放入if中,只有当坡度有变化时,才改变preDiff的值

【本题的思路是我想不到又有一点理解不了的,要多看】

代码

class Solution {
    public int wiggleMaxLength(int[] nums) {
        if (nums.length == 0 || nums.length == 1)   return nums.length;
        int preDiff = 0;
        int curDiff = 0;
        int count = 1;
        for (int i = 1;i < nums.length;i++){
             curDiff = nums[i] - nums[i-1];
             if ((curDiff > 0 && preDiff <= 0) || (curDiff < 0 && preDiff >= 0)){
                 count++;
                 preDiff = curDiff;
             }
        }
        return count;
    }
}

最大子序和

链接: 53. 最大子序和

思路

贪心算法类的题目有点像脑筋急转弯

本题主要是思考全面一点

不要用脑子想,你的脑子又不是很好使

写一些典型事例,考虑全面一点(这个是你需要训练的)

从一般题目抽象出一般规律,用代码表现出来,是一种建模能力。

多大胆假设,然后尝试用代码实现假设

注意点

1.sum = Integer.MIN_VALUE

我一开始想sum = 0;但是题目中有负数的情况,max(0,0+负数)肯定会选0,不可以。

2.我原来在判断时max(sum,sum+nums[i]),不可以,如果这样,则没办法舍弃原来的序列,构造新的序列,所以需要再设置一个count=0,来记录每一次连续子序列的和,不符合条件(count < 0),就让count = 0,重新开始记录新的序列

而sum = max(sum,count),sum 则是记录当前连续子序列最大和

3.count < 0,

count < 0时,count + nums[i] < nums[i] 我是求最大和,不是求最大长度,所以不如从nuns[i]开始重新记录一个新序列

代码

class Solution {
    public int maxSubArray(int[] nums) {
        int totalSum = Integer.MIN_VALUE;
        int curSum = 0;
        for (int i = 0;i < nums.length;i++){
            curSum += nums[i];
            totalSum = Math.max(totalSum,curSum);
            if (curSum < 0)  curSum = 0;
        }
        return totalSum;
    }
}

买卖股票的最佳时机Il

链接: 122.买卖股票的最佳时机II

思路

股票买卖nums[3] - nums[1],可以写成连加的形式nums[3]-nums[2] + nums[2] - nums[1] + nums[1] - nums[0]

所以本题求出该数组相邻元素的差值,选择均是正数的几天卖出(相加)

代码

class Solution {
    public int maxProfit(int[] prices) {
        if (prices.length == 1) return 0;
        int result = 0;
        for (int i = 1;i < prices.length;i++){
            if (prices[i] - prices[i - 1] > 0){
                result += (prices[i] - prices[i - 1]);
            }
        }
        return result;
    }
}

跳跃游戏

链接: 55. 跳跃游戏

思路

题目和思路都挺简单的,主要是细节的处理

最初位于数组的 第一个下标

数组中的每个元素代表你在该位置可以跳跃的最大长度。

那么我们遍历的范围应该是【0,coverRange】

自己的脑筋不够灵活,写成了

for (int i = 0;i <= nums.length;i++)

再三报错

一定要认真审题,审完题后在草稿纸上写一下过程,然后脑袋灵活一点建模,不可以全部凭借回忆。

代码

class Solution {
    public boolean canJump(int[] nums) {
        if (nums.length == 1)   return true;
        int cover = 0;
        for (int i = 0;i <= cover;i++){
            cover = Math.max(nums[i] + i,cover);
            if (cover >= nums.length - 1){
                return true;
            }
        }
        return false;
    }
}

跳跃游戏II

链接: 45.跳跃游戏II

思路

代码解析:

这种一般都是找一个接一下最大的范围

然后有个判断终止条件,其余的根据题意多设几个变量,灵活一点呗

代码

class Solution {
    public int jump(int[] nums) {
        if (nums.length == 1)   return 0;
        int curCover = 0;
        int maxCover = 0;
        int count = 0;
        for (int i = 0;i < nums.length;i++){
            maxCover = Math.max(maxCover,nums[i] + i);
            if (maxCover >= nums.length - 1){
                count++;
                break;
            }
            if (i == curCover){
                curCover = maxCover;
                count++;
            }
        }
        return count;
    }
}

K次取反后最大化的数组和

链接: 1005.K次取反后最大化的数组和

思路

本题是简单题目,因此用自己的传统思路解决了该问题

自己的思路

排序,排完序后将小于0的先全部变成大于0,然后统计还能取反的次数。

如果k值还有剩余,那么说明数组中的所有数值都应该是大于等于0的

取反后数组不再是有序的,再次排序,

如果剩余k值为偶数,相当于不变

如果k为奇数

选择第一个元素(绝对值最小的元素),将其取反

然后遍历数组元素,相加得到最后结果

官方思路

官方思路更清晰一点

那么本题的解题步骤为:

  • 第一步:将数组按照绝对值大小从大到小排序,注意要按照绝对值的大小
  • 第二步:从前向后遍历,遇到负数将其变为正数,同时K–
  • 第三步:如果K还大于0,那么反复转变数值最小的元素,将K用完
  • 第四步:求和

但是转换数据类型看起来很麻烦

代码

自己思路的代码

class Solution {
    public int largestSumAfterKNegations(int[] nums, int k) {
        Arrays.sort(nums);
        for (int i = 0;i < nums.length && k > 0;i++){
            if (nums[i] < 0){
                nums[i] = - nums[i];
                k--;
            }
        }
        Arrays.sort(nums);
        if (k % 2 != 0) nums[0] = -nums[0];
        int sum = 0;
        for (int i = 0;i < nums.length;i++){
            sum += nums[i];
        }
        return sum;
    }
}

别人思路的代码

//绝对值
class Solution {
    public int largestSumAfterKNegations(int[] nums, int K) {
    	// 将数组按照绝对值大小从大到小排序,注意要按照绝对值的大小
	nums = IntStream.of(nums)
		     .boxed()
		     .sorted((o1, o2) -> Math.abs(o2) - Math.abs(o1))
		     .mapToInt(Integer::intValue).toArray();
	int len = nums.length;	    
	for (int i = 0; i < len; i++) {
	    //从前向后遍历,遇到负数将其变为正数,同时K--
	    if (nums[i] < 0 && K > 0) {
	    	nums[i] = -nums[i];
	    	K--;
	    }
	}
	// 如果K还大于0,那么反复转变数值最小的元素,将K用完

	if (K % 2 == 1) nums[len - 1] = -nums[len - 1];
	return Arrays.stream(nums).sum();

    }
}

另一种思路

//有点绕,不太想看
class Solution {
    public int largestSumAfterKNegations(int[] A, int K) {
        if (A.length == 1) return k % 2 == 0 ? A[0] : -A[0];
        Arrays.sort(A);
        int sum = 0;
        int idx = 0;
        for (int i = 0; i < K; i++) {
            if (i < A.length - 1 && A[idx] < 0) {
                A[idx] = -A[idx];
                if (A[idx] >= Math.abs(A[idx + 1])) idx++;
                continue;
            }
            A[idx] = -A[idx];
        }

        for (int i = 0; i < A.length; i++) {
            sum += A[i];
        }
        return sum;
    }
}

加油站

链接: 134. 加油站

思路

相信自己的思路,在草稿纸上思维敏捷地想一想,不要总是否定自己

本题看起来有些复杂

例如从索引3出发到达索引4,得到4升油,消耗1升,最后剩余3升

自己一开始的思路总是异常直白,总是思考环状,很难去实现。

观察一下发现,gas[i] - cost[i] < 0时,则到达不了。

因此我们的思路是:

遍历数组,求从该索引开始环状遍历相加所求之和大于0的索引。

设置一个当前和curSum,这个是一直在变化的

设置一个总的和totalSum,这个一直遍历相加

设置一个我们的符合条件的索引 Index = 0;

遍历数组相加,如果 < 0,则curSum清零,重新统计新的索引开始之和;index = i + 1;(当前索引不符合条件,我们返回下一个索引嘛)

我之前没想明白否定自己思路的一点在于,如果从中间结点开始,要环状遍历一遍,我不知道该如何实现。

本题不需要考虑环状,前面遍历的节点不符合条件,保证开始索引之后的节点符合条件,这时候只要totalSum >=0就说明符合(在代码中 是 < 0 返回-1;否则直接return Index)

代码

class Solution {
    public int canCompleteCircuit(int[] gas, int[] cost) {   
        int totalSum = 0;
        int curSum = 0;
        int index = 0;
        for (int i = 0;i < gas.length;i++){
            curSum += gas[i] - cost[i];
            totalSum += gas[i] - cost[i];
            if (curSum < 0){
                curSum = 0;
                index = i + 1;
            }
        }
        if (totalSum < 0)   return -1;
        return index;
    }
}

分发糖果

链接: 135. 分发糖果

思路

本题思路自己比较模糊,想了像摆动序列一样,写成上下波动一样,但是自己在思考时总是漏一些情况。

本题两个条件:

  • 每个孩子至少分配到 1 个糖果。
  • 相邻的孩子中,评分高的孩子必须获得更多的糖果。

设置nums数组,记录第i个孩子能获得的糖果数量

条件一应将nums中的每个元素都赋值为1

然后从左向右遍历,如果左元素 < 右元素,则右元素的糖果数量比左元素的多一个;

然后从右向左遍历,如果左元素 > 右元素,则左元素的糖果数量应该在右元素的基础上+1,但有可能在上一个遍历上,左元素的糖果数量已经很大了,因此,我们用Math.max取最大。

遍历相加,即可

代码

class Solution {
    public int candy(int[] ratings) {
        int[] nums = new int[ratings.length];
        nums[0] = 1;
        for (int i = 1;i < ratings.length;i++){
            nums[i] = 1;
            if (ratings[i] > ratings[i - 1])
                nums[i] = nums[i - 1] + 1; 
        }
        for (int i = ratings.length - 2;i >= 0;i--){
            if (ratings[i] > ratings[i + 1])
            // 还是要落到纸上写一写
                nums[i] = Math.max(nums[i + 1] + 1,nums[i]);
        }
        int sum = 0;
        for (int i = 0;i < ratings.length;i++){
            sum += nums[i];
        }
        return sum;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值