• 贪心算法应用


    1. 算法思想

    贪心算法一般分为如下四步:

    • 将问题分解为若干个子问题
    • 找出适合的贪心策略
    • 求解每一个子问题的最优解
    • 将局部最优解堆叠成全局最优解

    选择每一阶段的局部最优,从而达到全局最优

    2. 最大自序和

    题目描述

    题目链接

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

    子数组 是数组中的一个连续部分。

    示例 1:

    输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
    输出:6
    解释:连续子数组 [4,-1,2,1] 的和最大,为 6
    • 1
    • 2
    • 3

    代码

    //贪心
    class Solution {
        public int maxSubArray(int[] nums) {
            int n = nums.length;
            //记录和
            int sum = Integer.MIN_VALUE;
            int count = 0;
    
            for(int i = 0;i < n;i++){
                count += nums[i];
                sum = Math.max(sum,count); // 取区间累计的最大值(相当于不断确定最大子序终止位置)
                //如果元素为负数,直接重置
                if(count < 0) count = 0; 
            }
    
            return sum;
        }
    }
    
    //DP
    class Solution {
        public int maxSubArray(int[] nums) {
            int n = nums.length;
            //定义dp数组
            int[] dp = new int[n];
            dp[0] = nums[0];
            int ans = dp[0];
    
            for(int i = 1;i < n;i++){
                dp[i] = Math.max(nums[i], dp[i - 1] + nums[i]);
                ans = Math.max(ans, dp[i]);
            }
    
            return ans;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    3. 跳跃游戏

    题目描述

    题目链接

    给定一个非负整数数组 nums ,你最初位于数组的 第一个下标

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

    判断你是否能够到达最后一个下标。

    示例 1:

    输入:nums = [2,3,1,1,4]
    输出:true
    解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 13 步到达最后一个下标。
    
    • 1
    • 2
    • 3

    代码

    class Solution {
        public boolean canJump(int[] nums) {
            if (nums.length == 1) {
                return true;
            }
            //覆盖范围, 初始覆盖范围应该是0,因为下面的迭代是从下标0开始的
            int coverRange = 0;
            //在覆盖范围内更新最大的覆盖范围
            for (int i = 0; i <= coverRange; i++) {
                coverRange = Math.max(coverRange, i + nums[i]);
                if (coverRange >= nums.length - 1) {
                    return true;
                }
            }
            return false;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    4. 加油站

    题目描述

    题目链接

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

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

    给定两个整数数组 gascost ,如果你可以绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1 。如果存在解,则 保证 它是 唯一 的。

    示例 1:

    输入: gas = [1,2,3,4,5], cost = [3,4,5,1,2]
    输出: 3
    解释:3 号加油站(索引为 3)出发,可获得 4 升汽油。此时油箱有 = 0 + 4 = 4 升汽油
    开往 4 号加油站,此时油箱有 4 - 1 + 5 = 8 升汽油
    开往 0 号加油站,此时油箱有 8 - 2 + 1 = 7 升汽油
    开往 1 号加油站,此时油箱有 7 - 3 + 2 = 6 升汽油
    开往 2 号加油站,此时油箱有 6 - 4 + 3 = 5 升汽油
    开往 3 号加油站,你需要消耗 5 升汽油,正好足够你返回到 3 号加油站。
    因此,3 可为起始索引。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    代码

    class Solution {
        public int canCompleteCircuit(int[] gas, int[] cost) {
          int n = gas.length;
          //当前剩余油量
          int curSum = 0;
          //总的剩余油量
          int tolSum = 0;
          //起点
          int startIndex = 0;
          for(int i = 0;i < n;i++){
              curSum += gas[i] - cost[i];
              tolSum += gas[i] - cost[i];
              if(curSum < 0){
                  startIndex = (i + 1) % n;
                  curSum = 0;
              }
          }
          //总剩余油量小于0,肯定没有起始点
          if(tolSum < 0) return -1;
          return startIndex;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    5. 分发糖果

    题目描述

    力扣链接

    n 个孩子站成一排。给你一个整数数组 ratings 表示每个孩子的评分。

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

    • 每个孩子至少分配到 1 个糖果。
    • 相邻两个孩子评分更高的孩子会获得更多的糖果。

    请你给每个孩子分发糖果,计算并返回需要准备的 最少糖果数目

    示例 1:

    输入:ratings = [1,0,2]
    输出:5
    解释:你可以分别给第一个、第二个、第三个孩子分发 212 颗糖果。
    
    • 1
    • 2
    • 3

    代码

    class Solution {
        public int candy(int[] ratings) {
    
             /**
             分两个阶段
             1、起点下标1 从左往右,只要 右边 比 左边 大,右边的糖果=左边 + 1
             2、起点下标 ratings.length - 2 从右往左, 只要左边 比 右边 大,此时 左边的糖果应该 取本身的糖果数(符合比它左边大) 和 右边糖果数 + 1 二者的最大值,这样才符合 它比它左边的大,也比它右边大
        */
            int n = ratings.length;
            int[] candies = new int[n];
            candies[0] = 1;
    
            //从前往后
            for(int i = 1;i < n;i++){
                 candies[i] = (ratings[i] > ratings[i - 1]) ? candies[i - 1] + 1 : 1;
            }
    
            //从后往前
            for(int i = n - 2;i >= 0;i--){
                 if (ratings[i] > ratings[i + 1]) {
                    candies[i] = Math.max(candies[i], candies[i + 1] + 1);
                }
            }
    
            int sum = 0;
            //计算
            for(int num : candies){
                sum += num;
            }
    
            return sum;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    根据身高重建队列

    题目描述

    题目链接

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

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

    示例 1:

    输入:people = [[7,0],[4,4],[7,1],[5,0],[6,1],[5,2]]
    输出:[[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]]
    解释:
    编号为 0 的人身高为 5 ,没有身高更高或者相同的人排在他前面。
    编号为 1 的人身高为 7 ,没有身高更高或者相同的人排在他前面。
    编号为 2 的人身高为 5 ,有 2 个身高更高或者相同的人排在他前面,即编号为 01 的人。
    编号为 3 的人身高为 6 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。
    编号为 4 的人身高为 4 ,有 4 个身高更高或者相同的人排在他前面,即编号为 0123 的人。
    编号为 5 的人身高为 7 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。
    因此 [[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]] 是重新构造后的队列。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    代码

    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[]> que = new LinkedList<>();
    
            for (int[] p : people) {
                //表示在链表指定位置插入元素
                // [[7,0], [7,1], [6,1], [5,0], [5,2],[4,4]]
                //[7,0]:表示在索引为0处插入[7,0](索引相同,前插)
                que.add(p[1],p);
            }
    
            return que.toArray(new int[people.length][]);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    注意

    • 分发糖果盒根据升高重建队列,这两个题目都是有两个维度,遇到两个维度权衡的时候,一定要先确定一个维度,再确定另一个维度

    6. 无重叠区间

    题目描述

    题目链接

    给定一个区间的集合 intervals ,其中 intervals[i] = [starti, endi] 。返回 需要移除区间的最小数量,使剩余区间互不重叠

    示例 1:

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

    示例 2:

    输入: intervals = [ [1,2], [1,2], [1,2] ]
    输出: 2
    解释: 你需要移除两个 [1,2] 来使剩下的区间没有重叠。
    
    • 1
    • 2
    • 3

    示例 3:

    输入: intervals = [ [1,2], [2,3] ]
    输出: 0
    解释: 你不需要移除任何区间,因为它们已经是无重叠的了。
    
    • 1
    • 2
    • 3

    代码

    class Solution {
        public int eraseOverlapIntervals(int[][] intervals) {
    
            //按照左边界排序
            Arrays.sort(intervals, (a, b) -> Integer.compare(a[0],b[0]));
    
            int count = 0;
            int n = intervals.length;
            for(int i = 1;i < n;i++){
                //判断两个区间是否有重叠
                if(intervals[i - 1][1] > intervals[i][0]){
                    count++;
                    //记录最小右边界
                    intervals[i][1] = Math.min(intervals[i][1], intervals[i - 1][1]);
                }
            }
    
            return count;
    
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    相似题目

    用最少数量的箭引爆气球

    7. 划分字母区间

    题目描述

    力扣链接

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

    示例:

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

    提示:

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

    思路

    可以分为如下两步:

    • 统计每一个字符最后出现的位置
    • 从头遍历字符,并更新字符的最远出现下标,如果找到字符最远出现位置下标和当前下标相等了,则找到了分割点

    代码

    class Solution {
    public:
        vector<int> partitionLabels(string S) {
            int hash[27] = {0}; // i为字符,hash[i]为字符出现的最后位置
            for (int i = 0; i < S.size(); i++) { // 统计每一个字符最后出现的位置
                hash[S[i] - 'a'] = i;
            }
            vector<int> result;
            int left = 0;
            int right = 0;
            for (int i = 0; i < S.size(); i++) {
                right = max(right, hash[S[i] - 'a']); // 找到字符出现的最远边界
                if (i == right) {
                    result.push_back(right - left + 1);
                    left = i + 1;
                }
            }
            return result;
        }
    };
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    8. 合并区间

    题目描述

    力扣链接

    以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间

    示例 1:

    输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
    输出:[[1,6],[8,10],[15,18]]
    解释:区间 [1,3][2,6] 重叠, 将它们合并为 [1,6].
    
    • 1
    • 2
    • 3

    示例 2:

    输入:intervals = [[1,4],[4,5]]
    输出:[[1,5]]
    解释:区间 [1,4][4,5] 可被视为重叠区间。
    
    • 1
    • 2
    • 3

    提示:

    • 1 <= intervals.length <= 104
    • intervals[i].length == 2
    • 0 <= starti <= endi <= 104

    代码

    class Solution {
        public int[][] merge(int[][] intervals) {
            int n = intervals.length;
            if(n == 1) return intervals;
            //按照左边界排序
            Arrays.sort(intervals,(a,b)->Integer.compare(a[0],b[0]));
            //存储返回数组
            int[][] res = new int[n][2];
            //定义数据的大小
            int size = 0;
            int i = 1;
            for(;i < n;i++){
                //比较两个元素的右边界和左边界
                if(intervals[i-1][1] < intervals[i][0]){
                    res[size][0] = intervals[i-1][0];
                    res[size][1] = intervals[i-1][1];
                    size++;     
                }else{//更新左边界和右边界
                    intervals[i][0] = intervals[i-1][0];
                    intervals[i][1] = Math.max(intervals[i-1][1], intervals[i][1]);
                }
            }
            //处理最后一个元素  
            res[size][0] = intervals[i - 1][0];
            res[size][1] = intervals[i - 1][1];
            size++; 
            return Arrays.copyOf(res,size);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    9. 单调递增的数字

    题目描述

    力扣链接

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

    给定一个整数 n ,返回 小于或等于 n 的最大数字,且数字呈 单调递增

    示例 1:

    输入: n = 10
    输出: 9
    
    • 1
    • 2

    示例 2:

    输入: n = 1234
    输出: 1234
    
    • 1
    • 2

    示例 3:

    输入: n = 332
    输出: 299
    
    • 1
    • 2

    提示:

    • 0 <= n <= 109

    思路

    • 本题只要想清楚个例,例如98,一旦出现strNum[i - 1] > strNum[i]的情况(非单调递增),首先想让strNum[i - 1]减一,strNum[i]赋值9,这样这个整数就是89

    代码

    class Solution {
        public int monotoneIncreasingDigits(int n) {
           /**
                98 -> 89
            */
    
            String s = String.valueOf(n);
            char[] chars = s.toCharArray();
            int len = chars.length;
            int start = len;
            // 98
            for(int i = len - 2;i >= 0;i--){
                if (chars[i] > chars[i + 1]) {
                    chars[i]--; // 9 - > 8
                    start = i + 1; // 记录该位置,8 - > 9
                }
            }
            for (int i = start; i < len; i++) {
                chars[i] = '9';
            }
    
            return Integer.parseInt(String.valueOf(chars));
        }
    
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    参考

    代码随想录

  • 相关阅读:
    React工具
    【刷题笔记7】LeetCode 54. 螺旋矩阵(数组模拟)
    ORACLE-递归查询、树操作
    Spring循环依赖
    医院预约挂号系统,java医院预约挂号系统,医院预约挂号管理系统毕业设计作品
    c语言分层理解(c语言结构体(下))
    备战2024秋招面试题-对比Java、Go和Python
    【JAVA面试八股文】之并发和多线程
    关于网络安全运营工作与安全建设工作的一些思考
    C语言编程经典100例——11至20例
  • 原文地址:https://blog.csdn.net/qq_42130468/article/details/127980353