leetcode刷题——贪心算法

发布于:2024-12-20 ⋅ 阅读:(19) ⋅ 点赞:(0)

贪心的本质是选择每一阶段的局部最优,从而达到全局最优

455.分发饼干

力扣题目链接(opens new window)

假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。

对每个孩子 i,都有一个胃口值  g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。

//对2个数组排序,然后再从后往前逐个分配饼干,也就是胃口大的先被满足
class Solution {
    public int findContentChildren(int[] g, int[] s) {
        Arrays.sort(g);
        Arrays.sort(s);
        int res = 0;
        if (s.length == 0 || g.length == 0)
            return res;
        for (int i = g.length - 1, j = s.length - 1; i >= 0; i--) {
            if (j >= 0 && s[j] >= g[i]) {
                res++;
                j--;
            }
        }
        return res;

    }
}

376. 摆动序列

力扣题目链接(opens new window)

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

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

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

只保留局部峰值,把上升和下降的峰值保留,中间的数字就不要统计,通过差值来比较

我的思路:用pre1和pre2两个指针保存前面2个不同的需要比较的值,这道题需要考虑出现多个相等值的情况,所有我用2个指针,只会保存不相等的2个值和当前的nums[i]作比较

这2个指针的更新需要注意,是每个nums[i]都需要判断更新,而不是满足摆动条件才更新

class Solution {
    public int wiggleMaxLength(int[] nums) {
        if (nums.length == 1)
            return 1;
        int res = 1;//初始化长度为1
        int pre1 = nums[0];
        int pre2 = nums[0];
        int index = 0;
        //找到pre2的位置和值
        for (int i = 1; i < nums.length; i++) {
            index = i;
            if (nums[i] != pre1) {
                pre2 = nums[i];           
                break;
            }
            
        }
        //找到不相同的数字,那就结果已经有2个长度满足了
        if (pre1 != pre2)
            res = 2;

        //从pre2的下一个位置开始遍历
        for (int i = index + 1; i < nums.length; i++) {
            //差值为一正一负,那就是相乘小于0
            if ((nums[i] - pre2) * (pre2 - pre1) < 0) {
                res++;
            }
            //这里注意,只有nums[i]!=pre2 我们才去更新pre1 pre2
            //每个位置都会判断更新pre1 pre2
            if (nums[i] != pre2) {
                pre1 = pre2;
                pre2 = nums[i];
            }
        }
        return res;

    }
}

53. 最大子序和

力扣题目链接(opens new window)

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

示例:

  • 输入: [-2,1,-3,4,-1,2,1,-5,4]
  • 输出: 6
  • 解释:  连续子数组  [4,-1,2,1] 的和最大,为  6。
class Solution {
//思路:count不断累积元素值,如果count是负数,那就重置为0
//因为负数累加的值会更小,需要恢复为0继续相加下一个元素
    public int maxSubArray(int[] nums) {
        int res=nums[0];
        int count=0;
        for(int i=0;i<nums.length;i++){
            count+=nums[i];
            res=Math.max(res,count);
            if(count<0){
                count=0;
            }          
        }
        return res;
        
    }
}

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

力扣题目链接(opens new window)

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

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

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

思路:只需要收集每天的正利润

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;
        
    }
}

55. 跳跃游戏

力扣题目链接(opens new window)

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

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

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

class Solution {
    public boolean canJump(int[] nums) {
        if (nums.length == 1)
            return true;
        int maxStep = 0;
        int step = 0;
        
        for (int i = 0; i <= maxStep; i++) {
            //计算当前元素可以到达的最大位置
            //这里注意,要保证当前位置是可以到达的,这个就通过i <= maxStep判断
            step = i + nums[i];
            //遍历过程中,计算保留最大的可以到达的位置
            maxStep = Math.max(maxStep, step);
            //如果最大可以到达的位置超过数组大小,那肯定是true
            if (maxStep >= nums.length - 1)
                return true;
        }
        return false;

    }
}

45.跳跃游戏 II

力扣题目链接(opens new window)

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

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

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

贪心:在 i 能够跳跃到 i+j 的这个区间中,找出我们要跳跃的位置,也就是可以求出最大索引的元素,就相当于第一次跳到了这个,然后就继续循环遍历这个元素能够到达的区间,继续求最大索引,就尽量保证都是跳最远,花费的跳跃次数最少

class Solution {
    public int jump(int[] nums) {
        if(nums.length==1) return 0;
        int maxStep=nums[0];
        int count=0;
        int res=0;
        //for循环遍历元素最大跳跃到的位置,在这个区间中又继续求出最大的索引值
        //保留作为下一次继续遍历的边界
        for(int i=0;i<=maxStep;i++){
            count=Math.max(count,i+nums[i]);
            //结束一次maxstep的遍历时,就跳了一次 记录最大到达的位置count赋值给maxstep
            //改变maxstep后又会继续遍历后面的一次跳跃
            if(i==maxStep) {
                res++;
                maxStep=count;
            }
            if(maxStep>=nums.length-1) return res+1;
        }
        return res;
        
    }
}

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

力扣题目链接(opens new window)

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

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

思路:先把所有的负数反过来,然后遇到0就直接把k变为0

如果k还剩余,就进行排序,把最小的正整数反复变化,如果k是偶数,那变化后还是正数

如果是奇数,就需要sum减去第一个元素值。

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

        k = k % 2;
        if (k == 1) {
            Arrays.sort(nums);
            sum -= 2 * nums[0];
        }
        return sum;

    }
}

134. 加油站

力扣题目链接(opens new window)

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

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

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

说明:

  • 如果题目有解,该答案即为唯一答案。
  • 输入数组均为非空数组,且长度相同。
  • 输入数组中的元素均为非负数。
class Solution {
    public int canCompleteCircuit(int[] gas, int[] cost) {

        int index = 0;
        int sum = 0;
        int count = 0;
        for (int i = 0; i < gas.length; i++) {
            int diff = gas[i] - cost[i];
            // 求出所有的剩余油量,如果小于0,肯定跑不完
            sum += diff;
            // 记录区间内的求和,如果小于0,说明从0到i的位置都不能作为起点,
            // 需要找下一个i+1 重新把count变为0
            count += diff;
            if (count < 0) {
                index = i + 1;
                count = 0;
            }
        }

        if (sum < 0)
            return -1;
        return index;
    }
}

135. 分发糖果

力扣题目链接(opens new window)

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

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

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

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

思路:采用两次贪心的策略:

  • 一次是从左到右遍历,只比较右边孩子评分比左边大的情况。
  • 一次是从右到左遍历,只比较左边孩子评分比右边大的情况。

class Solution {
    public int candy(int[] ratings) {
        if (ratings.length == 1)
            return 1;
        int[] candy = new int[ratings.length];
        candy[0] = 1;
        int count = 0;
        for (int i = 1; i < ratings.length; i++) {
            candy[i] = 1;
            if (ratings[i] > ratings[i - 1])
                candy[i] = candy[i - 1] + 1;
        }
        for (int i = ratings.length - 2; i >= 0; i--) {
            //这里需要注意用到Math.max,因为要维持原来的值,又需要保证右边的元素
            if (ratings[i] > ratings[i + 1])
                candy[i] = Math.max(candy[i], candy[i + 1] + 1);
        }
        for (int i = 0; i < ratings.length; i++) {
            count += candy[i];
        }
        return count;

    }
}

860.柠檬水找零

力扣题目链接(opens new window)

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

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

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

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

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

针对20的情况,优先找零10+5 不够再5+5+5  因为5留给后面用处更多

class Solution {
    public boolean lemonadeChange(int[] bills) {
        int five=0;
        int ten=0;
        for(int i=0;i<bills.length;i++){
            if(bills[i]==5) five++;
            if(bills[i]==10){
                if(five<=0) return false;
                five--;
                ten++;
            }
            if(bills[i]==20){
                if(ten>0){
                    ten--;
                    five--;
                }
                else{
                    five-=3;                  
                }
                if(five<0||ten<0) return false;
            }
        }
        return true;
        
    }
    
}

406.根据身高重建队列

力扣题目链接(opens new window)

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

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

class Solution {
    public int[][] reconstructQueue(int[][] people) {
        //先按照身高排序从大到小,再按照k数量排序
        Arrays.sort(people,(a,b)->{
            if(a[0]==b[0]) return a[1]-b[1];
            return b[0]-a[0];
        });
        //数组也是对象,可以用链表存储
        LinkedList<int[]> queue=new LinkedList();
        for(int[] aa:people){
            //linkedlist的add(int index,E element);
            //比较巧妙:排序后,从前向后遍历,先把所有高的元素排好,k就是要排序的位置,
            //比如遍历到44 那当前queue的所有元素都是高度超过4的,
            // 那44的k为4表示前面4个人高过它,所以直接插入到索引4
            queue.add(aa[1],aa);
        }
        return queue.toArray(new int[0][]);
        
    }
}

452. 用最少数量的箭引爆气球

力扣题目链接(opens new window)

在二维空间中有许多球形的气球。对于每个气球,提供的输入是水平方向上,气球直径的开始和结束坐标。由于它是水平的,所以纵坐标并不重要,因此只要知道开始和结束的横坐标就足够了。开始坐标总是小于结束坐标。

一支弓箭可以沿着 x 轴从不同点完全垂直地射出。在坐标 x 处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstart,xend, 且满足  xstart ≤ x ≤ xend,则该气球会被引爆。可以射出的弓箭的数量没有限制。 弓箭一旦被射出之后,可以无限地前进。我们想找到使得所有气球全部被引爆,所需的弓箭的最小数量。

给你一个数组 points ,其中 points [i] = [xstart,xend] ,返回引爆所有气球所必须射出的最小弓箭数。

class Solution {
    public int findMinArrowShots(int[][] points) {
        // 使用Integer内置比较方法,不会溢出
        Arrays.sort(points, (a, b) -> Integer.compare(a[0], b[0]));
        int res = 0;
        for (int i = 1; i < points.length; i++) {
            // 不重叠的情况 该气球的左边界大于上一个气球的右边界
            if (points[i][0] > points[i - 1][1]) {
                res++;
            } else {
                // 重叠就更新需要下次比较的右边界
                points[i][1] = Math.min(points[i][1], points[i - 1][1]);
            }
        }
        return res + 1;

    }
}

435. 无重叠区间

力扣题目链接(opens new window)

给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。

注意: 可以认为区间的终点总是大于它的起点。 区间 [1,2] 和 [2,3] 的边界相互“接触”,但没有相互重叠。

示例 1:

  • 输入: [ [1,2], [2,3], [3,4], [1,3] ]
  • 输出: 1
  • 解释: 移除 [1,3] 后,剩下的区间没有重叠。

这道题目和上一个类似,贪心在于碰到重叠的就需要进行删除,保留右边界最小的,为了后续的区间能保留更多

class Solution {
    public int eraseOverlapIntervals(int[][] intervals) {
        Arrays.sort(intervals,(a,b)->Integer.compare(a[0],b[0]));
        int res=0;
        for(int i=1;i<intervals.length;i++){
            if(intervals[i][0]<intervals[i-1][1]){
                intervals[i][1]=Math.min(intervals[i][1],intervals[i-1][1]);
                res++;
            }
        }
        return res;
    }
}

763.划分字母区间

力扣题目链接(opens new window)

字符串 S 由小写字母组成。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。返回一个表示每个字符串片段的长度的列表。

示例:

  • 输入:S = "ababcbacadefegdehijhklij"
  • 输出:[9,7,8] 解释: 划分结果为 "ababcbaca", "defegde", "hijhklij"。 每个字母最多出现在一个片段中。 像 "ababcbacadefegde", "hijhklij" 的划分是错误的,因为划分的片段数较少。

提示:

  • S的长度在[1, 500]之间。
  • S只包含小写字母 'a' 到 'z' 。

利用一个数组存储每个字母最后出现的位置,遍历字符串,

当i==end表示满足了【同一字母最多出现在一个片段中】的条件

class Solution {
    public List<Integer> partitionLabels(String s) {
        int[] letter=new int[200];
        List<Integer> res=new ArrayList();
        for(int i=0;i<s.length();i++){
            letter[s.charAt(i)]=i;
        }
        int end=letter[s.charAt(0)];
        int diff=0;
         for(int i=0;i<s.length();i++){        
            end=Math.max(end, letter[s.charAt(i)]);
            if(i==end){
                res.add(i+1-diff);
                diff=i+1;            
            } 
        }
        return res;

        
    }
}

56. 合并区间

力扣题目链接(opens new window)

给出一个区间的集合,请合并所有重叠的区间。

示例 1:

  • 输入: intervals = [[1,3],[2,6],[8,10],[15,18]]
  • 输出: [[1,6],[8,10],[15,18]]
  • 解释: 区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
class Solution {
    public int[][] merge(int[][] intervals) {
        Arrays.sort(intervals,(a,b)->Integer.compare(a[0],b[0]));
        List<int[]> res=new ArrayList();
        int min=intervals[0][0];
        int max=intervals[0][1];
        for(int i=1;i<intervals.length;i++){
            if(intervals[i][0]>max) {
                // int[] val=new int[2];
                // val[0]=min;
                // val[1]=max;
                res.add(new int[]{min,max});
                min=intervals[i][0];
                max=intervals[i][1];
            }
            else{
                max=Math.max(max,intervals[i][1]);
            }

        }
        res.add(new int[]{min,max});
        return res.toArray(new int[0][]);

        
    }
}

738.单调递增的数字

力扣题目链接(opens new window)

给定一个非负整数 N,找出小于或等于 N 的最大的整数,同时这个整数需要满足其各个位数上的数字是单调递增。

(当且仅当每个相邻位数上的数字 x 和 y 满足 x <= y 时,我们称这个整数是单调递增的。)

示例 1:

  • 输入: N = 10
  • 输出: 9

class Solution {
    public int monotoneIncreasingDigits(int n) {
//从后往前遍历,遇到不满足条件的数字,就进行-1,保留最后那个不满足条件的索引位置index
//从index后面开始,都变成9 因为前面减1后那后面都需要变成9
       
        StringBuilder sb=new StringBuilder(n+"");
        int index=sb.length();
        for(int i=sb.length()-1;i>=1;i--){
            char val=sb.charAt(i-1);
            if(sb.charAt(i)<sb.charAt(i-1)){
                sb.setCharAt(i-1,(char)(val-1));
                index=i-1;
            }
        }
        for(int i=index+1;i<sb.length();i++){
           sb.setCharAt(i,'9');
        }
        return Integer.parseInt(sb.toString());
        
    }
}

968.监控二叉树

力扣题目链接(opens new window)

给定一个二叉树,我们在树的节点上安装摄像头。

节点上的每个摄影头都可以监视其父对象、自身及其直接子对象。

计算监控树的所有节点所需的最小摄像头数量。

class Solution {
    // 贪心:摄像头放在叶子节点的父节点上面 后序遍历,从下往上遍历,
    // 每个节点3种状态0-无覆盖; 1-有摄像头;2-有覆盖
    int res=0;
    public int minCameraCover(TreeNode root) {
        // 还需要额外判断根节点状态,为0那就必须再加一个摄像头
        if(treeCir(root)==0) res++;
        return res;
        
    }
    // 这个函数是求出当前节点的状态
    public int treeCir(TreeNode node){
        // 情况1:空节点表示有覆盖
        if(node==null) return 2;
        int left=treeCir(node.left);
        int right=treeCir(node.right); 
        // 情况2:左右孩子节点都有覆盖,那当前节点属于无覆盖,注意这里还不需要加入摄像头
        if(left==2&&right==2) return 0;
        // 情况3:左右节点至少一个没有覆盖,那当前节点必须要有摄像头,返回状态1
        else if(left==0||right==0) {
            res++;
            return 1;
        }
        // 情况4:左右节点至少一个有摄像头 , 那当前节点是有覆盖的;1-1,1-2,2-1 
        //这个情况都是有摄像头或者有覆盖的,无覆盖的情况已经在前面包含了
        else{
            return 2;        
        }           
    }
}


网站公告

今日签到

点亮在社区的每一天
去签到