贪心问题【java|代码随想录|刷题自存】

1.分发饼干

class Solution {
    public int findContentChildren(int[] g, int[] s) {
        //大饼干喂饱大胃口
        int num=0;
        int gLen=g.length-1;
        int sLen=s.length-1;
        Arrays.sort(g);
        Arrays.sort(s);
        int startIndex=sLen;
        for(int i=gLen;i>=0;i--){
            if(startIndex>=0&&g[i]<=s[startIndex]){
                startIndex--;
                num++;
            }
        }
        return num;
    }
}

2.摆动序列

如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为摆动序列。第一个差(如果存在的话)可能是正数或负数。少于两个元素的序列也是摆动序列。

例如, [1,7,4,9,2,5] 是一个摆动序列,因为差值 (6,-3,5,-7,3) 是正负交替出现的。相反, [1,4,7,2,5] 和 [1,7,4,5,5] 不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。

给定一个整数序列,返回作为摆动序列的最长子序列的长度。 通过从原始序列中删除一些(也可以不删除)元素来获得子序列,剩下的元素保持其原始顺序。

  • 输入: [1,7,4,9,2,5]
  • 输出: 6
  • 解释: 整个序列均为摆动序列。
class Solution {
    public int wiggleMaxLength(int[] nums) {
        int len=nums.length;
        if(len<=1){
            return len;
        }
        int curDiff=0;
        int preDiff=0;
        int count=1;//初始化有一个值
        for(int i=1;i<len;i++){
            curDiff=nums[i]-nums[i-1];
            //考虑平坡
            if((curDiff>0&&preDiff<=0)||(curDiff<0&&preDiff>=0)){
                count++;
                preDiff=curDiff; //记得移动
            }
        }
        return count;
    }
}

3.最大子序和

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

示例:

  • 输入: [-2,1,-3,4,-1,2,1,-5,4]
  • 输出: 6
  • 解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
class Solution {
    public int maxSubArray(int[] nums) {
        int res = Integer.MIN_VALUE;
        int sum=0;
        for(int i=0;i<nums.length;i++){
            sum+=nums[i];
            res=Math.max(res,sum);
            //如果总和变为负数会拖累后面的总和
            if(sum<0){
                sum=0;
            }
        }
        return res;
    }
}

4.买卖股票的最佳时机2

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

示例 1:

  • 输入: [7,1,5,3,6,4]
  • 输出: 7
  • 解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4。随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。
class Solution {
    public int maxProfit(int[] prices) {
        if(prices==null||prices.length==0) return 0;
        int len= prices.length;
        int sum=0;
        for(int i=1;i<len;i++){
            if(prices[i]-prices[i-1]>0){ //只要比前一天盈利就可以
                sum+=prices[i]-prices[i-1];
            }
        }
        return sum;
    }
}

5.跳跃游戏

给定一个非负整数数组,你最初位于数组的第一个位置。

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

判断你是否能够到达最后一个位置。

示例 1:

  • 输入: [2,3,1,1,4]
  • 输出: true
  • 解释: 我们可以先跳 1 步,从位置 0 到达 位置 1, 然后再从位置 1 跳 3 步到达最后一个位置。
class Solution {
    public boolean canJump(int[] nums) {
        //应该用覆盖范围 而不是每次只取最远跳到哪里
        if(nums.length==1){
            return true;
        }
        int coverRange=0;//表示从第一个下标开始的覆盖范围
        //!!!不是遍历nums数组的长度 是遍历覆盖范围
        for(int i=0;i<=coverRange;i++){
            coverRange=Math.max(coverRange,i+nums[i]);
            if(coverRange>=nums.length-1){
                return true;
            }
        }
        return false;
    }
}

6.跳跃游戏2

给定一个非负整数数组,你最初位于数组的第一个位置。

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

你的目标是使用最少的跳跃次数到达数组的最后一个位置。

示例:

  • 输入: [2,3,1,1,4]
  • 输出: 2
  • 解释: 跳到最后一个位置的最小跳跃数是 2。从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。

说明: 假设你总是可以到达数组的最后一个位置。

class Solution {
    //注意题目假设一定可以到达末尾
    public int jump(int[] nums) {
        if (nums == null || nums.length == 0 || nums.length == 1) {
            return 0;
        }
        int curRange=0;//当前覆盖范围
        int maxRange = 0;//最大的覆盖范围
        int step=0;//初始化步数
        for(int i=0;i<nums.length;i++){
            maxRange=Math.max(maxRange,i+nums[i]);
            if(maxRange>=nums.length-1){ //假设下一步就可以走到终点
                step++;
                break;
            }
            //注意是如果当前走到了覆盖范围的末尾才要走下一步
            if(i==curRange){
                curRange=maxRange;
                step++;
            }

        }
        return step;
    }
}

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

给定一个整数数组 A,我们只能用以下方法修改该数组:我们选择某个索引 i 并将 A[i] 替换为 -A[i],然后总共重复这个过程 K 次。(我们可以多次选择同一个索引 i。)

以这种方式修改数组后,返回数组可能的最大和。

思路:

让绝对值大的负数变为正数,当前数值达到最大。

那么如果将负数都转变为正数了,K依然大于0,只找数值最小的正整数进行反转

注意区分负数全为正数后,k是奇数还是偶数。

class Solution {
    public int largestSumAfterKNegations(int[] nums, int k) {
        Arrays.sort(nums);
        int sum = 0;

        //尽量先把负数变成正数
        for(int i=0;i<nums.length&&k>0;i++){
            if(nums[i]<0){
                nums[i]=-nums[i];
                 k--;
            }
        }

        // 如果 k 还有剩余,检查 k 是否是奇数
        Arrays.sort(nums); // 重新排序,因为负数可能变成正数,影响最小值
        if (k % 2 == 1) { 
            nums[0] = -nums[0]; // 只翻转最小的数
        }

        // 计算最终的数组和
        for (int num : nums) {
            sum += num;
        }
        
        return sum;

    }
}

##8.加油站

在一条环路上有 N 个加油站,其中第 i 个加油站有汽油 gas[i] 升。

你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。

如果你可以绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1。

说明:

  • 如果题目有解,该答案即为唯一答案。
  • 输入数组均为非空数组,且长度相同。
  • 输入数组中的元素均为非负数。

思路:

首先如果总油量减去总消耗大于等于零那么一定可以跑完一圈,说明 各个站点的加油站 剩油量rest[i]相加一定是大于等于零的。

每个加油站的剩余量rest[i]为gas[i] - cost[i]。

i从0开始累加rest[i],和记为curSum,一旦curSum小于零,说明[0, i]区间都不能作为起始位置,因为这个区间选择任何一个位置作为起点,到i这里都会断油,那么起始位置从i+1算起,再从0计算curSum。

那么为什么一旦[0,i] 区间和为负数,起始位置就可以是i+1呢,i+1后面就不会出现更大的负数?

如果出现更大的负数,就是更新i,那么起始位置又变成新的i+1了。

那有没有可能 [0,i] 区间 选某一个作为起点,累加到 i这里 curSum是不会小于零呢? 如图:

img

如果 curSum<0 说明 区间和1 + 区间和2 < 0, 那么 假设从上图中的位置开始计数curSum不会小于0的话,就是 区间和2>0。

区间和1 + 区间和2 < 0 同时 区间和2>0,只能说明区间和1 < 0, 那么就会从假设的箭头初就开始从新选择起始位置了。

为什么 curSum 没有涵盖所有路线也能保证结果正确?

1. curSum 只是局部的,totalSum 代表整体

  • curSum 负责局部计算,用来检查当前的起点是否可行。
  • totalSum 负责整体判断,如果 totalSum < 0,则无解,直接返回 -1
  1. 如果 curSum 变成负数,意味着之前的所有尝试的起点都不可能是解
  • 如果从某个起点出发 curSum < 0,说明无论在哪个中间站点出发,都会导致无油可走,所以必须换起点。
  • 关键在于 如果 totalSum >= 0,那一定有解,并且解一定在 curSum 第一次变负数之后的 i+1 位置!
class Solution {
    public int canCompleteCircuit(int[] gas, int[] cost) {
        int curSum = 0; //当前路线上的剩余油量总和
        int totalSum = 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){
                index =(i+1)%gas.length;
                curSum=0;
            }
        }
        if(totalSum<0) return -1;
        return index;
    }
}

##9.分发糖果

师想给孩子们分发糖果,有 N 个孩子站成了一条直线,老师会根据每个孩子的表现,预先给他们评分。

你需要按照以下要求,帮助老师给这些孩子分发糖果:

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

那么这样下来,老师至少需要准备多少颗糖果呢?

示例 1:

  • 输入: [1,0,2]
  • 输出: 5
  • 解释: 你可以分别给这三个孩子分发 2、1、2 颗糖果。

示例 2:

  • 输入: [1,2,2]
  • 输出: 4
  • 解释: 你可以分别给这三个孩子分发 1、2、1 颗糖果。第三个孩子只得到 1 颗糖果,这已满足上述两个条件。

解析:

  • 分两种情况遍历:一种是右孩子大于左孩子,从左往右遍历。一种是左孩子大于右孩子,从右往左遍历。
  • 注意如果跟旁边的比较,相同没关系
class Solution {
    public int candy(int[] ratings) {
        int sum=0;
        int[] candy= new int[ratings.length];
        Arrays.fill(candy,1);
        //右孩子比左孩子分高
        for(int i=1;i<ratings.length;i++){
            if(ratings[i]>ratings[i-1]){
                candy[i]=candy[i-1]+1;
            }

        }
        //左孩子比右孩子分高
        for(int j=ratings.length-2;j>=0;j--){
            if(ratings[j]>ratings[j+1]){
                candy[j]=Math.max(candy[j],candy[j+1]+1);
            }    
        }
        for(int c:candy){
            sum+=c;
        }
        return sum;
    }
}

10.柠檬水找零

在柠檬水摊上,每一杯柠檬水的售价为 5 美元。

顾客排队购买你的产品,(按账单 bills 支付的顺序)一次购买一杯。

每位顾客只买一杯柠檬水,然后向你付 5 美元、10 美元或 20 美元。你必须给每个顾客正确找零,也就是说净交易是每位顾客向你支付 5 美元。

注意,一开始你手头没有任何零钱。

如果你能给每位顾客正确找零,返回 true ,否则返回 false 。

示例 1:

  • 输入:[5,5,5,10,20]
  • 输出:true
  • 解释:
    • 前 3 位顾客那里,我们按顺序收取 3 张 5 美元的钞票。
    • 第 4 位顾客那里,我们收取一张 10 美元的钞票,并返还 5 美元。
    • 第 5 位顾客那里,我们找还一张 10 美元的钞票和一张 5 美元的钞票。
    • 由于所有客户都得到了正确的找零,所以我们输出 true。
import java.util.HashMap;
import java.util.Map;

class Solution {
    public boolean lemonadeChange(int[] bills) {
        int five = 0, ten = 0; // 记录5美元和10美元的数量
        for (int bill : bills) {
            if (bill == 5) {
                five++; // 收到5美元,不需要找零
            } else if (bill == 10) {
                if (five > 0) {
                    five--; // 找零一个5美元
                    ten++; // 收到一个10美元
                } else {
                    return false; // 没有5美元找零,返回false
                }
            } else { // bill == 20
                if (ten > 0 && five > 0) { 
                    ten--; // 优先用一个10美元
                    five--; // 再用一个5美元
                } else if (five >= 3) { 
                    five -= 3; // 用三个5美元找零
                } else {
                    return false; // 无法找零
                }
            }
        }
        return true;
    }
}

11.根据升高重建队列

假设有打乱顺序的一群人站成一个队列,数组 people 表示队列中一些人的属性(不一定按顺序)。每个 people[i] = [hi, ki] 表示第 i 个人的身高为 hi ,前面 正好 有 ki 个身高大于或等于 hi 的人。

请你重新构造并返回输入数组 people 所表示的队列。返回的队列应该格式化为数组 queue ,其中 queue[j] = [hj, kj] 是队列中第 j 个人的属性(queue[0] 是排在队列前面的人)。

思路

对于本题相信大家困惑的点是先确定k还是先确定h呢,也就是究竟先按h排序呢,还是先按照k排序呢?

如果按照k来从小到大排序,排完之后,会发现k的排列并不符合条件,身高也不符合条件,两个维度哪一个都没确定下来。

那么按照身高h来排序呢,身高一定是从大到小排(身高相同的话则k小的站前面),让高个子在前面。

此时我们可以确定一个维度了,就是身高,前面的节点一定都比本节点高!

那么只需要按照k为下标重新插入队列就可以了,为什么呢?

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

排序完的people: [[7,0], [7,1], [6,1], [5,0], [5,2], [4,4]]

插入的过程:

  • 插入[7,0]:[[7,0]]
  • 插入[7,1]:[[7,0],[7,1]]
  • 插入[6,1]:[[7,0],[6,1],[7,1]]
  • 插入[5,0]:[[5,0],[7,0],[6,1],[7,1]]
  • 插入[5,2]:[[5,0],[7,0],[5,2],[6,1],[7,1]]
  • 插入[4,4]:[[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]]

class Solution {
    public int[][] reconstructQueue(int[][] people) {

       //先把身高按照从高到低排
        //然后从高到底 按照k值 对每一个人从前往后开始排序 
        //这样后面的按照k进行排序 就是基于前面的人 前面的人都是固定站位的了

        // 身高从大到小排(身高相同k小的站前面)
        Arrays.sort(people, (a, b) -> {
            if (a[0] == b[0]) return a[1] - b[1];   // a - b 是升序排列
            return b[0] - a[0];   //b - a 是降序排列
        });

        LinkedList<int[]> que = new LinkedList<>();

        for (int[] p : people) {
            que.add(p[1],p);   //Linkedlist.add(index, value)
        }

        return que.toArray(new int[people.length][]);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值