Leetcode刷题笔记——贪心篇

Leetcode刷题笔记——贪心篇

前言

一、贪心 + 双指针

第一题:种花问题

Leetcode605. 种花问题:简单题 (详情点击链接见原题)

假设有一个很长的花坛,一部分地块种植了花,另一部分却没有。可是,花不能种植在相邻的地块上,它们会争夺水源,两者都会死去

python代码解法

class Solution:
    def canPlaceFlowers(self, flowerbed: List[int], n: int) -> bool:
        index = 0
        while index < len(flowerbed) and n > 0:
            if flowerbed[index] == 1:   # 跳两格(因为相邻格不能种花,所以前一格必然为0)
                index += 2
            else:           # 如果flowerbed[index] == 0 只要考虑后一格是否为 0,为 0 即可种
                if index == len(flowerbed) - 1 or flowerbed[index + 1] == 0:
                    n -= 1
                    index += 2
                else:
                    index += 3
        return n == 0

第二题:盛最多水的容器

Leetcode11. 盛最多水的容器:中等题 (详情点击链接见原题)

给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i])
找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
返回容器可以储存的最大水量

python代码解法

class Solution:
    def maxArea(self, height: List[int]) -> int:
        max_area = 0
        left, right = 0, len(height) - 1
        while left < right:
            min_h = min(height[left], height[right])
            max_area = max(max_area, min_h * (right - left))
            if height[left] < height[right]:
                left += 1
            else:
                right -= 1
        return max_area

第三题: 救生艇

Leetcode881. 救生艇:中等题 (详情点击链接见原题)

给定数组 peoplepeople[i] 表示第 i 个人的体重 ,船的数量不限,每艘船可以承载的最大重量为 limit
每艘船最多可同时载两人,但条件是这些人的重量之和最多为 limit

解题思路:
一个直观的想法是,由于一个船要么载两人,要么载一人,在人数给定的情况下,为了让使用的总船数最小,要尽可能让更多船载两人,即尽可能多的构造出数量之和不超过 limit 的二元组
先对 people 进行排序,然后使用两个指针 leftright 分别从首尾开始进行匹配:

  • 如果 people[right] + people[left] > limit,说明不能成组,由于题目确保人的重量不会超过 limit,此时让 people[right]独立成船,船的数量加 1, right 指针左移
  • 如果 people[right] + people[left] <= limit,说明可以同船,此时船的数量加1,两个指针往中间靠拢

python代码解法

class Solution:
    def numRescueBoats(self, people: List[int], limit: int) -> int:
        people.sort()
        left, right = 0, len(people) - 1
        boat_num = 0
        while right >= left:
            if people[right] + people[left] > limit:
                right -= 1
            else:
                left += 1
                right -= 1
            boat_num += 1
        return boat_num

第四题:有效三角形的个数

Leetcode611. 有效三角形的个数:中等题 (详情点击链接见原题)

给定一个包含非负整数的数组 nums ,返回其中可以组成三角形三条边的三元组个数

解法1:双指针
首先对数组排序,固定最长的一条边,运用双指针扫描
如果nums[left] + nums[right] <= nums[i]left 右移进入下一轮
如果 nums[left] + nums[right] > nums[i],同时 nums[left + 1] + nums[right] > nums[i], nums[left + 2] + nums[right] > nums[i]....说明满足的条件有 right - left种,right 左移进入下一轮

python代码解法

class Solution:
    def triangleNumber(self, nums: List[int]) -> int:
        def is_triangle(x, y, z):
            if x + y > z:
                return True
            else:
                return False

        nums.sort()

        ans = 0
        for i in range(len(nums) - 1, 1, -1):
            left, right = 0, i - 1
            while left < right:
                if is_triangle(nums[left], nums[right], nums[i]):
                    ans += right - left    # 满足条件的有 right - left 种
                    right -= 1             # right 左移进入下一轮
                else:      
                    left += 1               # left 右移动进入下一轮
        return ans

第五题:通过删除字母匹配到字典里最长单词

Leetcode524. 通过删除字母匹配到字典里最长单词:中等题 (详情点击链接见原题)

给你一个字符串 s 和一个字符串数组 dictionary ,找出并返回 dictionary 中最长的字符串,该字符串可以通过删除 s 中的某些字符得到

考点:排序 + 双指针 + 贪心
我们可以先对 dictionary 根据题意进行自定义排序:

  1. 长度不同的字符串,按照字符串长度排倒序
  2. 长度相同的则按照字典序排升序

python中我们可以通过下面一行代码实现

# dictionary.sort(key=lambda x: (-len(x), x))
dictionary = sorted(dictionary, key=lambda x: (-len(x), x))

然后我们只需对 dictionary进行顺序查找,找到第一个符合条件的字符串即是答案,使用贪心+双指针实现来进行检查

  1. 使用两个指针 ij 分别扫描 sdictionary[x] 中的字符
  2. s[i] != dictionary[x][i]时,我们让i右移直到找到s中第一位与dictionary[x][j]对得上的位置,然后 ij 同时右移匹配下一个字符

python代码解法

class Solution:
    def findLongestWord(self, s: str, dictionary: List[str]) -> str:
        dictionary.sort(key=lambda x: (-len(x), x))
        for word in dictionary:
            word_index = 0
            s_index = 0
            while s_index < len(s) and word_index < len(word):
                if word[word_index] == s[s_index]:
                    word_index += 1
                s_index += 1
            if word_index == len(word):
                return word
        return ''

第六题:两地调度

Leetcode1029. 两地调度:中等题 (详情点击链接见原题)

公司计划面试 2n 人。给你一个数组 costs ,其中 costs[i] = [aCosti, bCosti] 。第 i 人飞往 a 市的费用为 aCosti ,飞往 b 市的费用为 bCosti

python代码解法

class Solution:
    def twoCitySchedCost(self, costs: List[List[int]]) -> int:
        costs.sort(key=lambda x: (x[0] - x[1]))
        # print(costs)
        left, right = 0, len(costs) - 1
        ans = 0
        while left < right:
            ans += costs[left][0] + costs[right][1]
            left += 1
            right -= 1
        return ans


if __name__ == '__main__':
    s = Solution()
    costs = [[10, 20], [30, 200], [400, 50], [30, 20]]
    print(s.twoCitySchedCost(costs))

二、贪心 + 哈希表

第一题:受标签影响的最大值

Leetcode1090. 受标签影响的最大值:中等题 (详情点击链接见原题)

我们有一个 n 项的集合。给出两个整数数组 valueslabels ,第 i 个元素的值和标签分别是 values[i]labels[i]。还会给出两个整数 numWanteduseLimit

解题思路
贪心的选择集合中值较大的元素,同时记录每个标签出现的次数,当某个标签出现的次数达到 useLimit 时,我们就不能再选择该标签对应的元素了
具体思路:

  1. 我们先将集合中的元素按照值从大到小进行排序
  2. 然后从前往后遍历排序后的元素,在遍历的过程中,我们使用一个哈希表hash_map 记录每个标签出现的次数,如果某个标签出现的次数达到了 useLimt,我们就跳过该元素,否则就将该元素的值加到最终的答案中,并将标签出现的次数加 1
  3. index遍历到末尾或者 numWanted 已达到要求则跳出循环,返回最终结果

python代码解法

class Solution:
    def largestValsFromLabels(self, values: List[int], labels: List[int], numWanted: int, useLimit: int) -> int:
        ans = []
        for v, l in zip(values, labels):
            ans.append((v, l))
        ans.sort(key=lambda x: x[0], reverse=True)
        res = 0
        hash_map = Counter()
        index = 0
        while index < len(values) and numWanted > 0:
            hash_map[ans[index][1]] += 1
            if hash_map[ans[index][1]] > useLimit:
                index += 1
                continue
            res += ans[index][0]
            numWanted -= 1
            index += 1
        return res

第二题:划分字母区间

Leetcode763. 划分字母区间:中等题 (详情点击链接见原题)

给你一个字符串 s 。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中

解题思路

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

python代码解法

class Solution:
    def partitionLabels(self, s: str) -> List[int]:
        hash_map = [0] * 27
        for index, value in enumerate(s):
            hash_map[ord(value) - ord('a')] = index  # 将每一个字母最远出现的下标进行统计
        result = []
        left, right = 0, 0
        for i, v in enumerate(s):
            right = max(right, hash_map[ord(v) - ord('a')])  # 从头遍历字符,并更新字符的最远出现下标
            if i == right:   # 如果找到字符最远出现位置下标和当前下标相等了,则找到了分割点
                result.append(right - left + 1)
                left = i + 1
        return result

第三题:连接两字母单词得到的最长回文串

Leetcode2131:连接两字母单词得到的最长回文串:中等题 (详情点击链接见原题)

给你一个字符串数组 wordswords 中每个元素都是一个包含 两个 小写英文字母的单词

python代码解法

class Solution:
    def longestPalindrome(self, words: List[str]) -> int:
        count = Counter()
        ans = 0
        for word in words:
            word_reverse = word[::-1]
            if word_reverse in count and count[word_reverse] > 0:
                ans += 4
                count[word_reverse] -= 1
            else:
                count[word] += 1
        for word, value in count.items():
            if word[0] == word[1] and value == 1:
                ans += 2
                break
        return ans

三、贪心 + 优先队列

第一题:最低加油次数

Leetcode871. 最低加油次数:困难题 (详情点击链接见原题)

汽车从起点出发驶向目的地,该目的地位于出发位置东面 target 英里处。
沿途有加油站,用数组 stations 表示。其中 stations[i] = [positioni, fueli] 表示第 i 个加油站位于出发位置东面 positioni 英里处,并且有 fueli 升汽油

解题思路
始终选择油量最大的加油站进行加油,可以确保不存在更优的结果
remian: 代表当前有多少油(起始remain = startFuel
loc: 代表走了多远
index: 代表经过的加油站的下标,n代表加油站总个数

python代码解法

from heapq import heappop, heappush


class Solution:
    def minRefuelStops(self, target: int, startFuel: int, stations: List[List[int]]) -> int:
        pq = []
        n, index = len(stations), 0  # n 表示加油站总个数,idx表示经过的加油站下标
        remain, loc, ans = startFuel, 0, 0  # remain表示当前有多少油,loc代表走了多远
        while loc < target:
            if remain == 0:
                if pq:
                    ans += 1
                    remain += -heappop(pq)  # 每次将remain累加到loc中(使用完剩余的油量可以去到的最远的距离)
                else:
                    return -1
            loc, remain = loc + remain, 0  # 更新距离和清空油量
            while index < n and stations[index][0] <= loc:  # 同时将loc内包含的加油站加入优先队列
                heappush(pq, -stations[index][1])  # 取相反数建立小根堆实则等价于用正数建立大根堆
                index += 1
        return ans

第二题:吃苹果的最大数目

Leetcode1705. 吃苹果的最大数目:中等题 (详情点击链接见原题)

有一棵特殊的苹果树,一连 n 天,每天都可以长出若干个苹果。在第 i 天,树上会长出 apples[i] 个苹果,这些苹果将会在 days[i] 天后(也就是说,第 i + days[i] 天时)腐烂,变得无法食用。也可能有那么几天,树上不会长出新的苹果,此时用 apples[i] =0days[i] = 0 表示

解题思路
直觉上我们会觉得【优先吃掉最快过期的苹果】是最优,而这个维护苹果过期的过程,可以使用小根堆来实现
(最后食用日期,当日产生苹果数量) 的形式存入小根堆进行维护
n:数组长度, time:当前时间, ans:吃到的苹果数量

  1. 首先,如果 time < n 或者堆不为空,说明还有苹果未被生成或者未被吃掉,继续模拟
  2. 在当日模拟中,如果 time < n 说明当天有苹果生成,先将苹果以二元组 (time + days[time] - 1, apples[time]) 的形式加入小根堆中
  3. 尝试从堆中取出【最后食用日期】,最早【可食用】的苹果,如果堆顶元素已过期,则抛弃
  4. 如果吃掉 cur 一个苹果后,仍有剩余,并且最后时间大于当前时间(尚未过期),将 cur 重新入堆
  5. 循环上述逻辑直到所有苹果出堆

python代码解法

import heapq


class Solution:
    def eatenApples(self, apples: List[int], days: List[int]) -> int:
        pq = []
        n = len(apples)
        time = 0  # 当前时间
        ans = 0   # 吃到的苹果数量
        while time < n or pq:  # 说明还有 苹果未被生成 或者 未被吃掉
            while pq and pq[0][0] <= time:
                heapq.heappop(pq)
            if time < n and apples[time] > 0:  # 说明当前有苹果生成(过滤当天没有苹果生成的情况)
                heapq.heappush(pq, [time + days[time], apples[time]])  # 以二元组形式(最后食用日期,当日产生苹果的数量)入堆
            if pq:
                # cur = heapq.heappop(pq)
                # if cur[0] > time and cur[1] >= 1:
                #     cur[1] -= 1
                #     heapq.heappush(pq, cur)
                pq[0][1] -= 1    # 吃掉一个苹果

                if pq[0][1] == 0:  # 如果没有剩余则出堆
                    heapq.heappop(pq)
                ans += 1
            time += 1
        return ans

四、区间问题中的贪心思路

第一题:合并区间

Leetcode56:合并区间:中等题 (详情点击链接见原题)

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

python代码解法

class Solution:
    def merge(self, intervals: List[List[int]]) -> List[List[int]]:
        # 先按照区间左端点进行从小到大排序
        intervals.sort(key=lambda x: x[0])               # 方法1
        # intervals = sorted(intervals, key=lambda x: x[0])   # 方法2
        merge = []
        for interval in intervals:
            if not merge or interval[0] > merge[-1][1]:  # 如果下一个区间的左端点大于上一个区间的右端点或者merge为空
                merge.append(interval)
            else:       # 如果下一个区间的左端点小于等于上一个区间的右端点,取两个区间右端点较大的那个
                merge[-1][1] = max(merge[-1][1], interval[1])

        return merge

第二题:删除被覆盖区间

Leetcode1288. 删除被覆盖区间:中等题 (详情点击链接见原题)

给你一个区间列表,请你删除列表中被其他区间所覆盖的区间。
只有当 c <= ab <= d 时,我们才认为区间 [a,b) 被区间 [c,d) 覆盖。在完成所有删除操作后,请你返回列表中剩余区间的数目

python代码解法

class Solution:
    def removeCoveredIntervals(self, intervals: List[List[int]]) -> int:
        intervals = sorted(intervals, key=lambda x: (x[0], -x[1]))
        print(intervals)
        count = 0
        i = 1
        temp = intervals[0]
        start = temp[0]
        end = temp[1]
        while i < len(intervals):
            if intervals[i][0] >= start and intervals[i][1] <= end:
                count += 1
            else:
                start = max(temp[0], intervals[i][0])
                end = max(temp[1], intervals[i][1])
                temp = intervals[i]
            i += 1
        return len(intervals) - count

第三题:用最少数量的箭引爆气球

Leetcode452. 用最少数量的箭引爆气球:中等题 (详情点击链接见原题)

有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points ,其中points[i] = [xstart, xend] 表示水平直径在 xstartxend 之间的气球。你不知道气球的确切 y 坐标

python代码解法1

class Solution:
    def findMinArrowShots(self, points: List[List[int]]) -> int:
        points.sort(key=lambda x: x[0])  # 对区间左端点进行从小到大排序
        merged = []
        for point in points:
            if not merged or merged[-1][1] < point[0]:
                merged.append(point)
            elif merged[-1][1] == point[0]:
                merged.pop(-1)
                merged.append([point[0], point[0]])
            else:
                merged[-1][0] = max(merged[-1][0], point[0])
                merged[-1][1] = min(merged[-1][1], point[1])
        return len(merged)

python代码解法2

第四题:无重叠区间

Leetcode435. 无重叠区间:中等题 (详情点击链接见原题)

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

python代码解法

class Solution:
    def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:
        intervals = sorted(intervals, key=lambda x: x[0])   # 以左边界排序
        # print(intervals)
        count = 0  # 用来统计重叠区间的数量
        i = 1
        end = intervals[0][1]
        while i < len(intervals):
            if intervals[i][0] >= end:  # 无重叠情况
                end = intervals[i][1]
            elif intervals[i][0] < end:  # 重叠情况
                end = min(intervals[i][1], end)
                count += 1
            i += 1
        return count

第五题:插入区间

Leetcode57. 插入区间:中等题 (详情点击链接见原题)

给你一个无重叠的 ,按照区间起始端点排序的区间列表,在列表中插入一个新的区间,你需要确保列表中的区间仍然有序且不重叠

python代码解法

class Solution:
    def insert(self, intervals: List[List[int]], newInterval: List[int]) -> List[List[int]]:
        intervals = sorted(intervals, key=lambda x: x[0])
        if not intervals:
            intervals.append(newInterval)
        i = 0
        merged = []
        while i < len(intervals) and intervals[i][1] < newInterval[0]:
            merged.append(intervals[i])
            i += 1

        while i < len(intervals) and intervals[i][0] <= newInterval[1]:
            newInterval[0] = min(newInterval[0], intervals[i][0])
            newInterval[1] = max(newInterval[1], intervals[i][1])
            i += 1
        merged.append([newInterval[0], newInterval[1]])

        while i < len(intervals) and merged[-1][1] < intervals[i][0]:
            merged.append(intervals[i])
            i += 1
        return merged

五、贪心 + 其他

第一题:分发饼干

Leetcode455. 分发饼干:简单题 (详情点击链接见原题)

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

解题思路:
这里的局部最优就是大饼干喂给胃口大的,充分利用饼干尺寸喂饱一个
全局最优就是喂饱尽可能多的小孩

python代码解法

class Solution:
    def findContentChildren(self, g: List[int], s: List[int]) -> int:
        s.sort()  # 饼干尺寸排序
        g.sort()  # 胃口排序
        ans = 0
        s_p, g_p = len(s) - 1, len(g) - 1
        while s_p >= 0 and g_p >= 0:
            if s[s_p] >= g[g_p]:
                ans += 1
                s_p -= 1
                g_p -= 1
            else:
                g_p -= 1
                continue
        return ans

第二题:柠檬水找零

Leetcode860. 柠檬水找零:简单题 (详情点击链接见原题)

在柠檬水摊上,每一杯柠檬水的售价为 5 美元。顾客排队购买你的产品,(按账单 bills 支付的顺序)一次购买一杯。
每位顾客只买一杯柠檬水,然后向你付 5 美元、10 美元或 20 美元。你必须给每个顾客正确找零,也就是说净交易是每位顾客向你支付 5 美元

解题思路:
因为只有三种面额,51020
case1: 顾客给 5 元,直接收下
case2:顾客给 10 元,消耗一个 5,增加一个 10
case3:顾客给 20,优先消耗一个 10 和一个 5,如果不够,再消耗三个 5

python代码解法

class Solution:
    def lemonadeChange(self, bills: List[int]) -> bool:
        i = 0
        five, ten, twenty = 0, 0, 0
        while i < len(bills):
            if bills[i] == 5:   # 情形1:顾客给 5 元,直接收下
                five += 1
            elif bills[i] == 10:   # 情形2:顾客给 10 元,消耗一个 5,增加一个 10
                if five < 1:
                    return False
                five -= 1
                ten += 1
            elif bills[i] == 20:   # 顾客给 20,优先消耗一个 10 和一个 5,如果不够,再消耗三个 5
                if ten >= 1:
                    if five >= 1:
                        ten -= 1
                        five -= 1
                    else:
                        return False
                else:
                    if five >= 3:
                        five -= 3
                    else:
                        return False
            i += 1
        return True

第三题:买卖股票的最佳时机

Leetcode121. 买卖股票的最佳时机:简单题 (详情点击链接见原题)

给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润

由于本题只有一笔交易(买入卖出),遍历一遍数组,计算每次到当天为止的最小股票价格和最大利润

python代码解法

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        lowest_price = float('inf')    # 最低的买入价格初始化为一个最大值
        max_profit = 0              # 最大的收益初始化为 0 (确保不能获取任何利润的情况下返回 0)
        for price in prices:
            lowest_price = min(lowest_price, price)
            max_profit = max(max_profit, price - lowest_price)
        return max_profit

第三题进阶:买卖股票的最佳时机 II

Leetcode122. 买卖股票的最佳时机 II:中等题 (详情点击链接见原题)

给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。
在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售

解题思路:
加入第 0 天买入,第 3 天卖出,那么利润为 prices[3] - prices[0],相当于 (prices[3] - prices[2]) + (prices[2] - prices[1]) + (prices[1] - prices[0]),此时把利润分解为以每天为单位的维度,而不是从 0 天 到第 3 天整体去考虑
收集正利润的区间,就是股票买卖的区间,而我们只需要关注最终利润,不需要记录区间
局部最优:收集每天的正利润
全局最优:求得最大利润

python代码解法

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        profit = []
        for i in range(1, len(prices)):
            profit.append(prices[i] - prices[i - 1])   # 1.收集每天的利润,组成从第一天开始收获利润的利润序列
        ans = 0
        for pro in profit:
            if pro > 0:       # 2.贪心:只收集每天的正利润
                ans += pro
        return ans

第四题:最大数

Leetcode179: 最大数:中等题 (详情点击链接见原题)

给定一组非负整数 nums,重新排列每个数的顺序(每个数不可拆分)使之组成一个最大的整数

思路:对于nums中的任意两个值ab,我们无法从常规角度上确定其大小(先后)关系,但是我们可以根据结果来决定 ab 的排序关系

如果拼接结果 abba 好,我们就认为 a 应该放在 b 的前面
3 + 30 > 30 + 3 ,所以我们认为3 > 30

python代码解法

class Solution:
    def largestNumber(self, nums: List[int]) -> str:
        def sort_rule(x, y):
            a, b = x + y, y + x
            if a < b: return 1
            elif a > b: return -1
            else: return 0
        
        strs = [str(num) for num in nums]
        strs.sort(key = cmp_to_key(sort_rule))
        if strs[0] == "0":
            return "0"
        return ''.join(strs)

第五题:最大子数组和

Leetcode53. 最大子数组和:中等题 (详情点击链接见原题)

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

如果 -2 1在一起计算最大子数组和的子数组的起始位置时,一定从1开始计算,因为负数只会拉低总和

局部最优:当前连续和为负数的时候立刻放弃,从下一个元素重新计算连续和,因为负数加上下一个元素连续和只会越来越小
全局最优: 选取最大连续和

python代码解法

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        right = 0
        total_sum = 0
        max_res = -float('inf')
        while right < len(nums):
            total_sum += nums[right]
            max_res = max(max_res, total_sum)
            if total_sum < 0:
                total_sum = 0
            right += 1
        return max_res

第六题:跳跃游戏

Leetcode55. 跳跃游戏:中等题 (详情点击链接见原题)

给你一个非负整数数组 nums ,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度

解题思路
其实跳几步无所谓,关键在于可跳的覆盖范围,不一定非要明确一次究竟跳几步,每次取最大的跳跃步数,这个就是可以跳跃的覆盖范围,在这个范围内不管怎么跳,反正一定可以跳过来
问题转换为跳跃覆盖范围究竟可不可以覆盖到终点
贪心算法局部最优解:每次移动取最大跳跃步数(得到最大覆盖范围)
整体最优解:最后得到整体最大覆盖范围看能否到终点

python代码解法

class Solution:
    def canJump(self, nums: List[int]) -> bool:
        if len(nums) == 1:  # 只有一个元素就能达到
            return True
        cover = 0
        i = 0
        while i <= cover:  # 注意这里是小于等于cover
            cover = max(cover, i + nums[i])
            if cover >= len(nums) - 1:  # 说明可以覆盖到终点了
                return True
            i += 1
        return False

第六题进阶:跳跃游戏 II

Leetcode45. 跳跃游戏 II:中等题 (详情点击链接见原题)

给定一个长度为 n0 索引整数数组 nums。初始位置为 nums[0]

总是可以到达数组的最后一个位置,本题求得是跳到终点的最小跳跃次数
错误思路:
局部最优:当前尽可能跳远点,如果还没到终点,跳数 + 1
整体最优:一步尽可能多跳从而达到最少跳数
因为不知道下一跳最远能跳到哪里,所以上述方法是错的

正确思路:
从覆盖范围出发,不管怎么跳,覆盖范围内一定是可以跳到的,以最小的步数增加覆盖范围,覆盖范围一旦覆盖了终点得到的就是最少步数
这里需要统计两个覆盖范围:当前这一步的覆盖范围和下一步的覆盖范围
如果移动下标达到了当前这一步覆盖范围的最远距离,还没有到达终点的话,那么就必须再走一步来增加当前覆盖范围,直到下一步的覆盖范围覆盖了终点

python代码解法

class Solution:
    def jump(self, nums: List[int]) -> int:
        if len(nums) == 1:
            return 0
        ans = 0
        cur_distance = 0
        nex_jump = 0
        for i in range(0, len(nums)):
            nex_jump = max(nex_jump, i + nums[i])  # 更新下一步覆盖最远距离下标
            if i == cur_distance:  # 遇到当前覆盖的最远距离下标
                ans += 1  # 跳数 + 1
                cur_distance = nex_jump  # 更新当前覆盖的最远距离下标(相当于加油了)
                if nex_jump >= len(nums) - 1:  # 当前覆盖最远距到达集合终点,不用做ans++操作了,直接结束
                    break
        return ans

第七题:保持城市天际线

Leetcode807:保持城市天际线:中等题 (详情点击链接见原题)

给你一座由 n x n 个街区组成的城市,每个街区都包含一座立方体建筑。给你一个下标从 0 开始的 n x n 整数矩阵 grid ,其中 grid[r][c] 表示坐落于 rc 列的建筑物的 高度

贪心思路:我们需要确保在调整建筑物的高度后,从水平竖直两个方向所看到的行和列的最大高度不变

  1. 我们可以先通过 O(n * m) 的复杂度预处理出 grid 中每行的最大值以及每列的最大值
  2. 在统计答案时,通过判断当前位置 grid[i][j]min(row_max[i], col_max[j]) 的大小关系来决定当前位置能够增高多少

局部最优:当每个位置都取得最大额度增大高度时
全局最优:整体的增加高度最大

python代码解法

class Solution:
    def maxIncreaseKeepingSkyline(self, grid: List[List[int]]) -> int:
        row, col = len(grid), len(grid[0])
        ans = 0

        row_max, col_max = [], []
        for i in range(row):
            r_max = 0
            c_max = 0
            for j in range(col):
                r_max = max(r_max, grid[i][j])
                c_max = max(c_max, grid[j][i])
            row_max.append(r_max)
            col_max.append(c_max)

        for x in range(row):
            for y in range(col):
                ans += min(row_max[x], col_max[y]) - grid[x][y]
        return ans

第八题:加油站

Leetcode134. 加油站:中等题 (详情点击链接见原题)

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

python代码解法

class Solution:
    def canCompleteCircuit(self, gas: List[int], cost: List[int]) -> int:
        totalSum, curSum = 0, 0
        start = 0
        for i in range(len(gas)):
            curSum += (gas[i] - cost[i])
            totalSum += (gas[i] - cost[i])
            if curSum < 0:
                start = i + 1   # 起始位置更新为i+1
                curSum = 0    # curSum从0开始
        if totalSum < 0:   # 说明怎么走都不可能跑完一圈了
            return -1 
        return start

第九题:森林中的兔子

Leetcode781. 森林中的兔子:中等题 (详情点击链接见原题)

森林中有未知数量的兔子。提问其中若干只兔子 “还有多少只兔子与你(指被提问的兔子)颜色相同?” ,将答案收集到一个整数数组 answers 中,其中 answers[i] 是第 i 只兔子的回答

结论:
1.同一颜色的兔子回答的数值必然是一样的
2.但回答同样数值的不一定就是同颜色的兔子
要我们求最少的兔子数量,不妨假设某种颜色的兔子有 m 只,它们回答的答案数值为 cnt,那么 mcnt 满足什么关系?
m = cnt + 1
如果在answer数组里,回答 有 cnt 的数量的兔子颜色与我相同的兔子数量为 t 的话,分情况讨论
t ≤ cnt + 1:为达到最少的兔子数量,我们可以假定这 t 只兔子为同一颜色【既能满足题意,又不会导致额外的兔子数量增加】
t > cnt + 1:我们知道回答数量为 cnt 的兔子应该有 cnt + 1 只,这时候说明有数量相同的不同颜色的兔子进行了回答,为达到最少的兔子数量,我们应当将 t 分为若干种颜色,并尽可能让某一种颜色的兔子为 cnt + 1只,这样能够满足题意

换句话说
局部最优:我们应该让【同一颜色的兔子数量】尽量多
全局最优:实现整体【总的兔子数量】最少

python代码解法

class Solution:
    def numRabbits(self, answers: List[int]) -> int:
        answers.sort()
        i, ans = 0, 0
        while i < len(answers):
            cur = answers[i]
            ans += (cur + 1)
            while cur > 0 and i + 1 < len(answers) and answers[i] == answers[i + 1]:
                cur -= 1
                i += 1
            i += 1
        return ans

第十题:根据身高重建队列

Leetcode406. 根据身高重建队列:中等题 (详情点击链接见原题)

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

解题思路
身高从大到小排(身高相同k小的站前面)
{7 0} {7 1} {6 1} {5 0} {5 2} {4 4}

按照身高排序后,优先按身高高的peoplek来插入,后面插入节点也不会影响前面已经插入的节点,最终按照k的规则完成了队列

第十一题:单调递增的数字

Leetcode738. 单调递增的数字:中等题 (详情点击链接见原题)

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

python代码解法(暴力解法)

class Solution:
    def check(self, n):
        while n:
            last = n % 10
            n //= 10
            if last < n % 10:
                return False
        return True

    def monotoneIncreasingDigits(self, n: int) -> int:
        if self.check(n):
            return n
        else:
            while n > 0:
                n -= 1
                if self.check(n):
                    return n

解题思路:

python代码解法(优化解法)

class Solution:
    def monotoneIncreasingDigits(self, n: int) -> int:
        st = list(str(n))   # 1.将整数转换为字符串
        flag = len(st)      # 2. flag 用来标记赋值 9 从哪里开始 
        for i in range(len(st) - 1, 0, -1):
            if st[i - 1] > st[i]:  # 3.如果当前字符比前一个字符小说明需要修改前一个字符
                st[i - 1] = str(int(st[i - 1]) - 1)   # 4.将前一个字符减1
                flag = i   

        for i in range(flag, len(st)):   # 5.将修改位置后面的字符都设置为9
            st[i] = '9'
        return int(''.join(st))   # 6.将列表转换为字符串,并将字符串转换为整数并返回

第十二题:分发糖果

Leetcode135. 分发糖果:困难题 (详情点击链接见原题)

n 个孩子站成一排。给你一个整数数组 ratings 表示每个孩子的评分。
你需要按照以下要求,给这些孩子分发糖果:每个孩子至少分配到 1 个糖果。
1.相邻两个孩子评分更高的孩子会获得更多的糖果。
2.请你给每个孩子分发糖果,计算并返回需要准备的 最少糖果数目

解题思路:
先确定一边,再确定另一边,例如比较每一个孩子的左边,然后再比较右边
先确定右边评分大于左边的情况(从前向后遍历)
局部最优:只要右边评分比左边大,右边的孩子就多一个糖果
全局最优:相邻的孩子中,评分高的右孩子获得比左边孩子更多的糖果
接下来:确定左孩子大于右孩子的情况一定要从后向前遍历
局部最优:如果 rating[i] > rating[i + 1],此时 candy[i]就有两个选择了,取 candy[i + 1]candy[i] 中最大的糖果数量,保证第 i 个小孩的糖果数量大于左右两边的
全局最优:相邻的孩子中,评分高的孩子获得更多的糖果

python代码解法

class Solution:
    def candy(self, ratings: List[int]) -> int:
        n = len(ratings)
        candy = [1] * n  # 1.初始化糖果数组,保证每个小孩至少分到1个糖果
        for i in range(n):   # 2.从前向后遍历
            if i > 0 and ratings[i] > ratings[i - 1]:
                candy[i] = candy[i - 1] + 1
        for j in range(n - 1, -1, -1):   # 3.从后向前遍历
            if j > 0 and ratings[j - 1] > ratings[j]:
                candy[j - 1] = max(candy[j - 1], candy[j] + 1)
        return sum(candy)

第十四题:最小操作次数使数组元素相等 II

Leetcode462. 最小操作次数使数组元素相等 II:中等题 (详情点击链接见原题)

给你一个长度为 n 的整数数组 nums ,返回使所有数组元素相等需要的最小操作数

python代码解法

class Solution:
    def minMoves2(self, nums: List[int]) -> int:
        left, right = 0, len(nums) - 1
        mid = (left + right) // 2
        nums.sort()
        ans = 0
        for num in nums:
            ans += abs(num - nums[mid])
        return ans

总结

本文给大家介绍了贪心算法在面试种的常见高频考点

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

code_lover_forever

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

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

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

打赏作者

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

抵扣说明:

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

余额充值