leetcode刷题(贪心算法)

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

例如,有一堆钞票,你可以拿走十张,指定每次拿最大的,最终结果就是拿走最大数额的钱。

每次拿最大的就是局部最优,最后拿走最大数额的钱就是推出全局最优。

反例:有一堆盒子,如何把体积为n的背包尽可能装满,如果还每次选最大的盒子,就不行了。这时候就需要动态规划。

想清楚局部最优,想清楚全局最优,感觉局部最优是可以推出全局最优,并想不出反例,就先试一试贪心。

  1. 分发饼干

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

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

示例 1:

输入: g = [1,2,3], s = [1,1]
输出: 1
解释: 你有三个孩子和两块小饼干,3个孩子的胃口值分别是:1,2,3。
虽然你有两块小饼干,由于他们的尺寸都是1,你只能让胃口值是1的孩子满足。 所以你应该输出1。

# python
class Solution:
    def findContentChildren(self, g: List[int], s: List[int]) -> int:
        g.sort()
        s.sort()
        start, count = len(s) - 1, 0
        for index in range(len(g) - 1, -1, -1):
            if start >= 0 and s[start] >= g[index]:
                start -= 1
                count += 1
        return count
// Java
class Solution {
    public int findContentChildren(int[] g, int[] s) {
        Arrays.sort(g);
        Arrays.sort(s);
        int start = s.length - 1;
        int count = 0;
        for (int index = g.length - 1; index >= 0; index--) {
            if (start >= 0 && s[start] >= g[index]) {
                start--;
                count++;
            }
        }
        return count;
    }
}
  1. 摆动序列

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

例如, [1, 7, 4, 9, 2, 5] 是一个 摆动序列 ,因为差值 (6, -3, 5, -7, 3) 是正负交替出现的。

相反,[1, 4, 7, 2, 5][1, 7, 4, 5, 5]不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。
子序列 可以通过从原始序列中删除一些(也可以不删除)元素来获得,剩下的元素保持其原始顺序。

给你一个整数数组 nums ,返回 nums 中作为 摆动序列 的 最长子序列的长度 。

示例 1:
输入:nums = [1,7,4,9,2,5]
输出:6
解释:整个序列均为摆动序列,各元素之间的差值为 (6, -3, 5, -7, 3) 。

# python
class Solution:
    def wiggleMaxLength(self, nums: List[int]) -> int:
        preC, curC, res = 0, 0, 1
        # 题目里nums长度大于等于1,当长度为1时,其实到不了for循环里去,所以不用考虑nums长度
        for i in range(len(nums) - 1):
            curC = nums[i + 1] - nums[i]
            if preC * curC <= 0 and curC != 0:  # 差值为0时,不算摆动
                res += 1
                preC = curC     # 当前差值和上一个差值为一正一负时,用当前差值替代上一个差值
        return res
// Java
class Solution {
    public int wiggleMaxLength(int[] nums) {
        int preDiff = 0;    // 上一差值
        int curDiff = 0;    // 当前差值
        int res = 1;
        for (int i = 1; i < nums.length; i++) {
            curDiff = nums[i] - nums[i - 1];
            // 如果当前差值和上一个差值为一正一负
            // 等于0的情况表示初始时的preDiff
            // if ((curDiff > 0 && preDiff <= 0) || (curDiff < 0 && preDiff >= 0)) {
            if (preDiff * curDiff <= 0 && curDiff != 0) {
                res++;
                preDiff = curDiff;
            }
        }
        return res;
    }
}
  1. 最大子数组和

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

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

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

# python
class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        res = -float('inf')
        count = 0
        for i in range(len(nums)):
            count += nums[i]
            if count > res:
                res = count     # 取区间累计的最大值(相当于不断确定最大子序终止位置)
            if count <= 0:
                count = 0   # 相当于重置最大子序起始位置,因为遇到负数一定是拉低总和
        return res
// Java
class Solution {
    public int maxSubArray(int[] nums) {
        if (nums.length == 1) {
            return nums[0];
        }
        int sum = Integer.MIN_VALUE;
        int count = 0;
        for (int i = 0; i < nums.length; i++) {
            count += nums[i];
            // sum = Math.max(count, sum);
            if (count > sum) sum = count;
            if (count <= 0) {
                count = 0;
            }
        }
        return sum;
    }
}
  1. 买卖股票的最佳时机II

给定一个数组 prices ,其中 prices[i] 表示股票第 i 天的价格。

在每一天,你可能会决定购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以购买它,然后在 同一天 出售。
返回 你能获得的 最大 利润 。

示例 1:

输入: prices = [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。

# python
class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        result = 0
        for i in range(1, len(prices)):
            result += max(prices[i] - prices[i - 1], 0)
        return result
// Java
class Solution {
    public int maxProfit(int[] prices) {
        int result = 0;
        for (int i = 1; i < prices.length; i++) {
            result += Math.max(prices[i] - prices[i - 1], 0);
        }
        return result;
    }
}
  1. 跳跃游戏

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

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

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

示例 1:

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

// python
class Solution:
    def canJump(self, nums: List[int]) -> bool:
        if len(nums) == 1: return True
        cover_range = 0
        i = 0
        # python不支持动态修改for循环中变量,使用while循环代替
        while i <= cover_range:
            cover_range = max(i + nums[i], cover_range)
            if cover_range >= len(nums) - 1:
                return True
            i += 1
        return False
// Java
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(i + nums[i], coverRange);
            if (coverRange >= nums.length - 1) return true;
        }
        return false;
    }
}
  1. 跳跃游戏II

给你一个非负整数数组 nums ,你最初位于数组的第一个位置。数组中的每个元素代表你在该位置可以跳跃的最大长度。你的目标是使用最少的跳跃次数到达数组的最后一个位置。假设你总是可以到达数组的最后一个位置。

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

# python
class Solution:
    def jump(self, nums: List[int]) -> int:
        if len(nums) == 1: return 0
        # 记录跳跃的次数
        count = 0
        # 当前能走到的最大区域
        cur_distance = 0
        # 下一步能走到的最大覆盖区域
        max_distance = 0
        for i in range(len(nums)):
            # 在可覆盖区域内逐步更新最大的覆盖区域
            max_distance = max(i + nums[i], max_distance)
            # 下一步的最大覆盖区域能走到终点,步数加一返回
            if (max_distance >= len(nums) - 1):
                count += 1
                return count
            # 用贪心的思想,直接走到当前能走的最大覆盖区域,步数加一
            if (i == cur_distance):
                cur_distance = max_distance
                count += 1
// Java
class Solution {
    public int jump(int[] nums) {
        if (nums.length == 1) return 0;
        int count = 0;
        int curDistance = 0;
        int maxDistance = 0;
        for (int i = 0; i < nums.length - 1; i++) {
            maxDistance = Math.max(i + nums[i], maxDistance);
            if (maxDistance >= nums.length - 1) {
                return ++count;
            }
            if (i == curDistance) {
                curDistance = maxDistance;
                count++;
            }
        }
        return count;
    }
}
  1. K次取反后最大化的数组和

给你一个整数数组 nums 和一个整数 k ,按以下方法修改该数组:
选择某个下标 i 并将 nums[i] 替换为 -nums[i] 。
重复这个过程恰好 k 次。可以多次选择同一个下标 i 。

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

示例 1:
输入:nums = [4,2,3], k = 1
输出:5
解释:选择下标 1 ,nums 变为 [4,-2,3] 。

# python
class Solution:
    def largestSumAfterKNegations(self, nums: List[int], k: int) -> int:
        nums = sorted(nums, key = abs, reverse = True)  # 将A按绝对值从大到小排列
        for i in range(len(nums)):                      # 优先把负值取反变正数
            if k > 0 and nums[i] < 0:                   # 注意k>0,否则会将所有负数取反
                nums[i] *= -1
                k -= 1
        if k > 0:                                       # 剩下的次数取反最小的正数
            nums[-1] *= (-1) ** k
        return sum(nums)
// Java
class Solution {
    public int largestSumAfterKNegations(int[] nums, int k) {
        nums = IntStream.of(nums)   // 变为 IntStream,使用Java8的流式操作
                .boxed()            // 变为 Stream<Integer>
                .sorted((o1, o2) -> Math.abs(o2) - Math.abs(o1)) // 按绝对值降序排序
                .mapToInt(Integer::intValue)    // 变为 IntStream
                .toArray();                     // 变为 int[]
        int n = nums.length;
        for (int i = 0; i < n; i++) {
            if (k > 0 && nums[i] < 0) {
                nums[i] *= -1;
                k--;
            }
        }
        if (k > 0) {
            nums[n -1] *= Math.pow(-1, k);  // 计算-1的k次方
        }
        return Arrays.stream(nums).sum();
    }
}
  1. 加油站

在一条环路上有 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 可为起始索引。

# python
class Solution:
    def canCompleteCircuit(self, gas: List[int], cost: List[int]) -> int:
        cur_sum = 0
        total_sum = 0
        start = 0
        for i in range(len(gas)):
            cur_sum += gas[i] - cost[i]
            total_sum += gas[i] - cost[i]   # 加油用油后还剩的量
            if cur_sum < 0:                 # 从剩余油量大于0的站点出发,否则下一站就无法到达
                cur_sum = 0                 # 从暂定的start开始,到剩余油量<0,说明断油了
                start = i + 1               # 如果能绕一圈,后面肯定有>0的剩余量填补,从该站点出发可保证中途不断油
        if total_sum < 0: return -1         # 总的剩余量大于等于0才有可能绕一圈
        return start
// Java
class Solution {
    public int canCompleteCircuit(int[] gas, int[] cost) {
        int curSum = 0;
        int totalSum = 0;
        int start = 0;
        for (int i = 0; i < gas.length; i++) {
            curSum += gas[i] - cost[i];
            totalSum += gas[i] - cost[i];
            if (curSum < 0) {
                curSum = 0;
                start = i + 1;
            }
        }
        if (totalSum < 0) {
            return -1;
        }
        return start;
    }
}
  1. 分发糖果

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

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

每个孩子至少分配到 1 个糖果。
相邻两个孩子评分更高的孩子会获得更多的糖果。
请你给每个孩子分发糖果,计算并返回需要准备的 最少糖果数目

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

# python
class Solution:
    def candy(self, ratings: List[int]) -> int:
        n = len(ratings)
        candy_vec = [1] * n
        for i in range(n - 1):                  # 1.先从左到右,当右边的大于左边的就加1
            if ratings[i + 1] > ratings[i]:
                candy_vec[i + 1] = candy_vec[i] + 1
        for j in range(n - 1, 0, -1):
            if ratings[j - 1] > ratings[j]:     # 从右往左, 如果左边大,按照贪心思想只加一即可,但如果第一次遍历有右>左的情况,满足条件的糖果数目可能比从右边加一的情况大,为满足条件,不得已将糖果数取二者中的最大值即可满足条件
                candy_vec[j - 1] = max(candy_vec[j - 1], candy_vec[j] + 1)
        return sum(candy_vec)
// Java
class Solution {
    public int candy(int[] ratings) {
        int[] candyVec = new int[ratings.length];
        candyVec[0] = 1;
        for (int i = 1; i < ratings.length; i++) {
            if (ratings[i] > ratings[i - 1]) {
                candyVec[i] = candyVec[i - 1] + 1;
            } else {
                candyVec[i] = 1;
            }
        }
        for (int j = ratings.length - 2; j >= 0; j--) {
            if (ratings[j] > ratings[j + 1]) {
                candyVec[j] = Math.max(candyVec[j], candyVec[j + 1] + 1);
            }
        }
        int sum = 0;
        for (int e : candyVec) {
            sum += e;
        }
        return sum;
    }
}
  1. 柠檬水找零

在柠檬水摊上,每一杯柠檬水的售价为 5 美元。顾客排队购买你的产品,(按账单 bills 支付的顺序)一次购买一杯。

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

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

给你一个整数数组 bills ,其中 bills[i] 是第 i 位顾客付的账。如果你能给每位顾客正确找零,返回 true ,否则返回 false

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

# python
class Solution:
    def lemonadeChange(self, bills: List[int]) -> bool:
        five, ten, twenty = 0, 0, 0
        for bill in bills:
            if bill == 5:                   # 收到5元,直接入账
                five += 1
            elif bill == 10:                # 收到10元,如果没有5元可以找,返回False
                if five == 0: return False
                five -= 1                   # 能找,则5元减少1张,10元多一张
                ten += 1
            else:                           # 其他为收到20元的情况
                if five > 0 and ten > 0:    # 有5元也有10元,各一张组合找零
                    five -= 1
                    ten -= 1
                    twenty += 1
                elif five > 2:              # 或者3张5元也能找零,其他情况则不能
                    five -= 3
                    twenty += 1
                else:
                    return False
        return True
// Java
class Solution {
    public boolean lemonadeChange(int[] bills) {
        int five = 0, ten = 0;
        for (int bill : bills) {
            if (bill == 5) five++;
            else if (bill == 10) {
                if (five == 0) return false;
                else {
                    five--;
                    ten++;
                }
            } else {
                if (five > 0 && ten > 0) {
                    five--;
                    ten--;
                } else if (five > 2) {
                    five -= 3;
                } else {
                    return false;
                }
            }
        }
        return true;
    }
}
  1. 根据身高重建队列

假设有打乱顺序的一群人站成一个队列,数组 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 个身高更高或者相同的人排在他前面,即编号为 0 和 1 的人。
编号为 3 的人身高为 6 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。
编号为 4 的人身高为 4 ,有 4 个身高更高或者相同的人排在他前面,即编号为
0、1、2、3 的人。
编号为 5 的人身高为 7 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。
因此 [[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]] 是重新构造后的队列。

# python
class Solution:
    def reconstructQueue(self, people: List[List[int]]) -> List[List[int]]:
        # 先按照h维度的身高顺序从高到低排序。确定第一个维度
        # lambda返回的是一个元组:当-x[0](维度h)相同时,再根据x[1](维度k)从小到大排序
        people.sort(key=lambda x: (-x[0], x[1]))
        que = []
        # 根据每个元素的第二个维度k,贪心算法,进行插入
        # people已经排序过了:同一高度时k值小的排前面。
        # 后面的值一定比前面的小,插入不影响前面的结果
        for p in people:
            que.insert(p[1], p)
        return que
// Java
class Solution {
    public int[][] reconstructQueue(int[][] people) {
        // 身高从大到小排(身高相同k小的站前面)
        Arrays.sort(people, (p1, p2)->{
            if (p1[0] == p2[0]) return (p1[1] - p2[1]);
            else {
                return p2[0] - p1[0];
            }
        });
        ArrayList<int[]> que = new ArrayList<int[]>();
        for (int[] p : people) {
            que.add(p[1], p);
        }
        return que.toArray(new int[people.length][]);
    }
}
  1. 用最少数量的箭引爆气球

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

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

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

示例 1:
输入:points = [[10,16],[2,8],[1,6],[7,12]]
输出:2
解释:对于该样例,x = 6 可以射爆 [2,8],[1,6] 两个气球,以及 x = 11 射爆另外两个气球

# python
class Solution:
    def findMinArrowShots(self, points: List[List[int]]) -> int:
        points.sort(key = lambda x: x[0])
        res = 1
        for i in range(1, len(points)):
            if points[i][0] > points[i - 1][1]: # 气球i和气球i-1没有重叠部分,=表示挨着也能引爆
                res += 1                        # 从索引1开始,第一箭无法引爆的,需加一
            else:                               # 有重叠,取最小右边界暂存于当前的[1]位置
                points[i][1] = min(points[i][1], points[i - 1][1])  # 只要后面的气球左边界与最小有边界有重叠都可以用前一支箭一同引爆
        return res 
// Java
class Solution {
    public int findMinArrowShots(int[][] points) {
        int res = 1;
        Arrays.sort(points, (o1, o2) -> Integer.compare(o1[0], o2[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][1], points[i][1]);
            }
        }
        return res;
    }
}
  1. 无重叠区间

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

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

# python
# 方法一:
class Solution:
    def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:
        intervals.sort(key = lambda x: (x[1]))    # 右边界从小到大排序
        end = -float('inf')
        res = 0
        for i in range(len(intervals)):     # 如果左边界小于前一区间的右边界,则有重叠,需移除
            if intervals[i][0] >= end:
                end = intervals[i][1]
            else:
                res += 1
        return res

# 方法二:
# 用上题射箭的思路,当有重叠出现时,保留右边界小的,删除右边界大的
class Solution:
    def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:
        intervals.sort(key = lambda x: (x[0]))    # 左边界从小到大排序
        res = 0
        for i in range(1,len(intervals)):     # 重叠出现时,保留右边界小的
            if intervals[i][0] < intervals[i - 1][1]:
                intervals[i][1] = min(intervals[i][1], intervals[i - 1][1])
                res += 1           
        return res
// Java
// 方法一:
class Solution {
    public int eraseOverlapIntervals(int[][] intervals) {
        Arrays.sort(intervals, (a, b) -> {
            if (a[1] == b[1]) return a[0] - b[0];
            return a[1] - b[1];
        });
        int end = Integer.MIN_VALUE;
        int res = 0;
        for (int i = 0; i < intervals.length; i++) {
            if (intervals[i][0] >= end) {
                end = intervals[i][1];
            } else {
                res++;
            }
        }
        return res;
    }
}

// 方法二:
// 用上题射箭的思路,当有重叠出现时,保留右边界小的,删除右边界大的
class Solution {
    public int eraseOverlapIntervals(int[][] intervals) {
        Arrays.sort(intervals, (a, b) -> {
            if (a[0] == b[0]) return a[1] - b[1];
            return a[0] - b[0];
        });
        int res = 0;
        for (int i = 1; i < intervals.length; i++) {
            if (intervals[i][0] < intervals[i - 1][1]) {
                res++;
                intervals[i][1] = Math.min(intervals[i][1], intervals[i - 1][1]);
            }
        }
        return res;
    }
}
  1. 划分字母区间

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

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

# python
class Solution:
    def partitionLabels(self, s: str) -> List[int]:
        hash = [0] * 26
        for i in range(len(s)):             # 统计每一个字符最后出现的位置
            hash[ord(s[i]) - ord('a')] = i  # ord()返回对应字符的 ASCII 数值
        res = []
        left = 0
        right = 0
        for i in range(len(s)):
            right = max(right, hash[ord(s[i]) - ord('a')])
            if i == right:  # 如果找到之前遍历过的所有字母的最远边界,说明这个边界就是分割点了
                res.append(right - left + 1)
                left = right + 1
        return res
// Java
class Solution {
    public List<Integer> partitionLabels(String s) {
        int[] hash = new int[26];
        for (int i = 0; i < s.length(); i++) {
            hash[s.charAt(i) - 'a'] = i;    // char[] chars = s.toCharArray(); chars[i] - 'a'
        }
        List<Integer> res = new ArrayList<Integer>();
        int left = 0;
        int right = 0;
        for (int i = 0; i < s.length(); i++) {
            right = Math.max(right, hash[s.charAt(i) - 'a']);
            if (i == right) {
                res.add(right - left + 1);
                left = right + 1;
            }
        }
        return res;
    }
}
  1. 合并区间

以数组 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].

# python
class Solution:
    def merge(self, intervals: List[List[int]]) -> List[List[int]]:
        intervals.sort(key = lambda x: x[0])    # 按左边界从小到大排序
        res = []
        res.append(intervals[0])                # 暂时把第一个数放进去
        for i in range(1, len(intervals)):
            res_last = res[-1]                  # 把前面放进去的数取出,右边界与其它做比较
            if intervals[i][0] <= res_last[1]:  # 如果接下来的左区间比右边界小,说明有重叠
                res[-1] = [res_last[0], max(res_last[1], intervals[i][1])]  # 修改,把重叠部分合并
            else:
                res.append(intervals[i])        # 如果没重叠部分,正常加入结果集
        return res
// Java
class Solution {
    public int[][] merge(int[][] intervals) {
        // Arrays.sort(intervals, (a, b) -> {
        //     if (a[0] == b[0]) return a[1] - b[1];
        //     return a[0] - b[0];
        // });
        Arrays.sort(intervals, (o1, o2) -> Integer.compare(o1[0], o2[0]));
        List<int[]> res = new ArrayList<>();
        res.add(intervals[0]);
        for (int i = 1; i < intervals.length; i++) {
            int[] last = res.get(res.size() - 1);
            if (intervals[i][0] <= last[1]) {
                res.set(res.size() - 1, new int[]{last[0], Math.max(last[1], intervals[i][1])});
            } else {
                res.add(intervals[i]);
            }
        }
        return res.toArray(new int[res.size()][]);
    }
}
  1. 单调递增的数字

当且仅当每个相邻位数上的数字 xy 满足 x <= y 时,我们称这个整数是单调递增的。
给定一个整数 n ,返回 小于或等于 n 的最大数字,且数字呈 单调递增

示例 1:
输入: n = 10
输出: 9

# python
class Solution:
    def monotoneIncreasingDigits(self, n: int) -> int:
        # 一旦n[i - 1] > n[i]的情况(非单调递增),让strNum[i - 1]减一,strNum[i]赋值9
        # 从后往前遍历即可
        a = list(str(n))
        for i in range(len(a) - 1, 0, -1):
            if a[i] < a[i - 1]:
                a[i - 1] = str(int(a[i - 1]) - 1)
                a[i:] = '9' * (len(a) - i)  # 325678,遇到3比2大,变为299999,后面都得变9
        return int(''.join(a))
// Java
// 版本一:创建String数组,使用Integer.parseInt方法将字符串转成int型数字,导致不管是耗时还是空间占用都非常高
class Solution {
    public int monotoneIncreasingDigits(int n) {
        String[] nStr = (n + "").split("");
        int start = nStr.length;
        for (int i = nStr.length - 1; i > 0; i--) {
            if (Integer.parseInt(nStr[i]) < Integer.parseInt(nStr[i - 1])) {
                nStr[i - 1] = (Integer.parseInt(nStr[i - 1]) - 1) + "";
                start = i;
            } 
        }
        for (int i = start; i < nStr.length; i++) {
            nStr[i] = "9";
        }
        return Integer.parseInt(String.join("", nStr));
    }
}

// 版本二
// 在char数组上原地修改
class Solution {
    public int monotoneIncreasingDigits(int n) {
        char[] nChar = Integer.toString(n).toCharArray();   // 数字字符可以直接减一
        int start = Integer.MAX_VALUE; //start初始值设为最大值,本身递增,没有数字需要改成9
        for (int i = nChar.length - 1; i > 0; i--) {
            if (nChar[i] < nChar[i - 1]) {
                nChar[i - 1] -= 1;
                start = i;
            } 
        }
        StringBuilder res = new StringBuilder();
        for (int i = 0; i < nChar.length; i++) {
            if (i >= start){
                res.append('9');
            } else {
                res.append(nChar[i]);
            }
        }
        return Integer.parseInt(res.toString());
    }
}
  1. 买卖股票的最佳时机含手续费

给定一个整数数组 prices,其中 prices[i]表示第 i 天的股票价格 ;整数 fee 代表了交易股票的手续费用。

你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。

返回获得利润的最大值。

注意:这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。

示例 1:
输入:prices = [1, 3, 2, 8, 4, 9], fee = 2
输出:8
解释:能够达到的最大利润:
在此处买入 prices[0] = 1
在此处卖出 prices[3] = 8
在此处买入 prices[4] = 4
在此处卖出 prices[5] = 9
总利润: ((8 - 1) - 2) + ((9 - 4) - 2) = 8

# pthon
class Solution:
    def maxProfit(self, prices: List[int], fee: int) -> int:
        res = 0
        min_price = prices[0]
        for i in range(1, len(prices)):
            if prices[i] < min_price:   # 等到价格最低再入手,如5,3,6,从5到6不如从3到6利润大
                min_price = prices[i]
            elif prices[i] >= min_price and prices[i] < min_price + fee:
                continue   # 如果价格<=min_price + fee,不能交易,否则利润为负
            else:   # 如果后一天的价格比前天高,继续持有股票,所有利润加起来才是一次交易的利润
                res += prices[i] - min_price - fee
                min_price = prices[i] - fee # 为防止后面持有利润也扣钱,得把fee减掉
        return res 
// Java
class Solution {
    public int maxProfit(int[] prices, int fee) {
        int res = 0;
        int minPrice = prices[0];
        for (int i = 1; i < prices.length; i++) {
            if (prices[i] < minPrice) {     // 当前价格比前天的低,适合买入
                minPrice = prices[i];       // 在持有的情况下,当前价格得比前天低超过fee才能买入
            } else if (prices[i] >= minPrice && prices[i] <= minPrice + fee){
                continue;   // 保持原有状态,因为此时买则不便宜,卖则亏本
            } else {
                // 计算利润,可能有多次计算利润,最后一次计算利润才是真正意义的卖出
                res += prices[i] - minPrice - fee;
                minPrice = prices[i] - fee; // 在明天收获利润的时候,才不会多减一次手续费
            }
        }
        return res;
    }
}
  1. 监控二叉树

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

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

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

示例 1:
在这里插入图片描述

输入:[0,0,null,0,0]
输出:1
解释:如图所示,一台摄像头足以监控所有节点。

# python
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def minCameraCover(self, root: TreeNode) -> int:
        # 从下往上安装摄像头:跳过leaves这样安装数量最少,局部最优 -> 全局最优
        # 先给leaves的父节点安装,然后每隔两层节点安装一个摄像头,直到Head
        # 0: 该节点未覆盖
        # 1: 该节点有摄像头
        # 2: 该节点有覆盖
        global res
        res = 0
        # 从下往上遍历:后序(左右中)
        def traversal(cur):
            global res
            if not cur: return 2
            left = traversal(cur.left)
            right = traversal(cur.right)
            # Case 1:
            # 左右节点都有覆盖
            if left == 2 and right == 2:
                return 0
            # Case 2:
                # 至少有一个节点无覆盖
            if left == 0 or right == 0:
                res += 1
                return 1
            # Case 3:
                # 左右节点都有摄像头或者有覆盖
                # 一边有摄像头,一边无覆盖属于第二种情况
            if left == 1 or right == 1:
                return 2
        if traversal(root) == 0:
            res += 1
        return res
// Java
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    private int res;
    public int minCameraCover(TreeNode root) {
        if (traversal(root) == 0) {
            res++;
        }
        return res;
    }
    private int traversal(TreeNode cur) {
        if (cur == null) return 2;
        int left = traversal(cur.left);
        int right = traversal(cur.right);
        if (left == 2 && right == 2) {
            return 0;
        }
        if (left == 0 || right == 0) {
            res ++;
            return 1;
        }
        if (left == 1 || right == 1) {
            return 2;
        }
        return 0;
    }
}
根据引用\[1\]和引用\[2\]的内容,推荐的LeetCode刷题顺序是按照题目类型刷题,优先选择树、链表、二分查找、DFS、BFS、动态规划等常见类型的题目。可以先做2~4道简单题,然后再做中等难度的题目。在选择题目时,可以优先选择题目序号小、点赞多、提交成功率高的题目,这样可以从简单入手,节省时间。同时,LeetCode每道题目都有“模拟面试”功能,可以给自己设定时间限制,如果做不出来可以看答案,然后记住思路后再自己尝试一遍。每种类型的题目做完10+道后,可以总结规律。 根据引用\[3\]的内容,题目可以按照不同的分类进行刷题,比如数组贪心算法、子数组贪心算法、子序列与贪心算法数字与贪心、单调栈法、双指针法等。可以根据自己的兴趣和需求选择相应的题目进行刷题。 综上所述,LeetCode刷题顺序可以按照题目类型或者题目分类进行选择。 #### 引用[.reference_title] - *1* [LeetCode 刷题顺序,按标签分类,科学刷题!](https://blog.csdn.net/fengyuyeguirenenen/article/details/125099023)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [leetcode 刷题指南 & 刷题顺序](https://blog.csdn.net/qijingpei/article/details/125561071)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [leetcode-刷题顺序推荐](https://blog.csdn.net/weixin_38087674/article/details/114107841)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天涯小才

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值