【记录】leetcode专项刷题(python版)

跟着灵神一起学习~

(一)贪心算法

一、策略

两种基本贪心策略:

  • 从最小/最大开始贪心,优先考虑最小/最大的数,从小到大/从大到小贪心。在此基础上衍生出了反悔贪心。
  • 从最左/最右开始贪心,思考第一个数/最后一个数的贪心策略,把n个数的原问题转换为n-1个数(或更少)的子问题。

1、从最小/最大开始贪心

(1)重新分装苹果(3074)

在这里插入图片描述

class Solution:
    def minimumBoxes(self, apple: List[int], capacity: List[int]) -> int:
        capacity.sort(reverse=True)
        n = len(capacity)
        apple_sum = sum(apple)
        ans = 0
        for i in range(n):
            if capacity[i] < apple_sum:
                ans += 1
                apple_sum -= capacity[i]
            else:
                ans += 1
                break
        return ans
  • 时间复杂度:O(m+nlogn),m为apple的长度,n为capacity的长度
  • 空间复杂度:O(1)
(2)装满石头的背包的最大数量(2279)

在这里插入图片描述

class Solution:
    def maximumBags(self, capacity: List[int], rocks: List[int], additionalRocks: int) -> int:
        n = len(capacity)
        remains = [0] * n
        for i in range(n):
            remains[i] = capacity[i] - rocks[i]
        remains.sort()
        ans = 0
        for i in range(n):
            if remains[i] != 0 and additionalRocks >= remains[i]:
                ans += 1
                additionalRocks -= remains[i]
            elif remains[i] == 0:
                ans += 1
        return ans
  • 时间复杂度:O(n+nlogn)
  • 空间复杂度:O(n)
(3)雪糕的最大数量(1833)

在这里插入图片描述

class Solution:
    def maxIceCream(self, costs: List[int], coins: int) -> int:
        costs.sort()
        ans = 0
        n = len(costs)
        for i in range(n):
            if coins >= costs[i]:
                coins -= costs[i]
                ans += 1
            else:
                break
        return ans
  • 时间复杂度:O(n+nlogn)
  • 空间复杂度:O(1)
(4)K次取反后最大化的数组和(1005)

在这里插入图片描述

class Solution:
    def largestSumAfterKNegations(self, nums: List[int], k: int) -> int:
        nums.sort()
        n = len(nums)
        sum = 0
        for i in range(n):
            if nums[i] < 0 and k > 0:
                nums[i] = -nums[i]
                k -= 1
            sum += nums[i]
        nums.sort()
        //如果k=0,表示所有的负数都变成正数,直接返回sum
        //如果k还剩但为偶数,直接抵销就行,因为负数已经全部转换完了,如果再变列表中的正数,sum只会越来越小
        if k == 0 or (k > 0 and k % 2 == 0):
            return sum
        else:
        //如果k还剩但为奇数,那说明肯定会有至少一个数变成负数,那就直接变最小的那个数让sum尽可能大
            return sum - 2 * nums[0]
  • 时间复杂度:O(n+nlogn)
  • 空间复杂度:O(1)
(5)不同整数的最少数目(1481)

在这里插入图片描述

class Solution:
    def findLeastNumOfUniqueInts(self, arr: List[int], k: int) -> int:
        kinds = collections.Counter(arr)
        kinds = sorted(kinds.items(),key=lambda x:x[1])
        n = len(kinds)
        ans = n
        for key,val in kinds:
            if val <= k:
                ans -= 1
                k -= val
            else:
                break
        return ans
  • 时间复杂度:O(m+nlogn),m是arr的长度,n是kinds的长度
  • 空间复杂度:O(n)
(6)非递增顺序的最小子序列(1403)

在这里插入图片描述

class Solution:
    def minSubsequence(self, nums: List[int]) -> List[int]:
        nums.sort(reverse=True)
        total_sum = sum(nums)
        cur_sum = 0
        ans = []

        for num in nums:
            cur_sum += num
            ans.append(num)
            if cur_sum > total_sum - cur_sum:
                break

        return ans
  • 时间复杂度:O(n+nlogn)
  • 空间复杂度:O(n)
(7)将数组分成最小总代价的子数组I(3010)

在这里插入图片描述
这道题我只能说非常的脑筋急转弯==
开头一个子数组的代价绝对是nums[0],其他两个子数组的代价找剩余数组中最小的两个元素就可以了

class Solution:
    def minimumCost(self, nums: List[int]) -> int:
        n = len(nums)
        if n == 3:
            return sum(nums)
        ans = nums[0]
    
        nums = nums[1:]
        nums.sort()
        ans = ans + nums[0] + nums[1]
        return ans
  • 时间复杂度:O(nlogn)
  • 空间复杂度:O(n)
(8)数组大小减半(1338)

在这里插入图片描述

class Solution:
    def minSetSize(self, arr: List[int]) -> int:
        kinds = collections.Counter(arr)
        kinds = sorted(kinds.items(),key=lambda x:x[1],reverse=True)
        total_size = len(arr)
        current_size = 0
        ans = 0
        for _,val in kinds:
            current_size += val
            ans += 1
            if current_size >= total_size // 2:
                return ans
  • 时间复杂度:O(m+nlogn),n是arr长度,m是kinds长度
  • 空间复杂度:O(m)
(9)卡车上的最大单元数(1710)

在这里插入图片描述

class Solution:
    def maximumUnits(self, boxTypes: List[List[int]], truckSize: int) -> int:
        boxTypes.sort(key=lambda x:x[1],reverse=True)
        ans = 0

        for numberOfBoxes,numberOfUnitsPerBox in boxTypes:
            if truckSize <= 0:
                break
            number = min(numberOfBoxes,truckSize)
            ans += number * numberOfUnitsPerBox
            truckSize -= number
        return ans
  • 时间复杂度:O(n+nlogn)
  • 空间复杂度:O(n)
(10)幸福值最大化的选择方案(3075)

在这里插入图片描述

class Solution:
    def maximumHappinessSum(self, happiness: List[int], k: int) -> int:
        #每次选幸福值最大的孩子
        happiness.sort(reverse=True)
        n = len(happiness)
        ans = happiness[0]
        i = 1
        times = 1
        while i < k:
            happiness[i] -= times
            if happiness[i] < 0:
                happiness[i] = 0
            ans += happiness[i]
            times += 1
            i += 1
   
        return ans
  • 时间复杂度:O(k+nlogn)
  • 空间复杂度:O(1)
(11)从一个范围内选择最多整数I(2554)

在这里插入图片描述

class Solution:
    def maxCount(self, banned: List[int], n: int, maxSum: int) -> int:
        ans = 0
        cur_sum = 0
        visited = set(banned)
        for i in range(1,n+1):
            if i not in visited:
                if cur_sum + i <= maxSum:
                    cur_sum += i
                    ans += 1
                else:
                    break
        return ans
  • 时间复杂度:O(n)
  • 空间复杂度:O(n)
(12)摧毁小行星(2126)

在这里插入图片描述

class Solution:
    def asteroidsDestroyed(self, mass: int, asteroids: List[int]) -> bool:
        asteroids.sort()
        n = len(asteroids)
        i = 0
        if mass < asteroids[0]:
            return False
        cur_mass = mass
        for i in range(n-1):
            cur_mass += asteroids[i]
            if cur_mass < asteroids[i+1]:
                return False
        return True
  • 时间复杂度:O(n+nlogn)
  • 空间复杂度:O(1)
(13)重排数组以得到最大前缀分数(2587)

在这里插入图片描述

class Solution:
    def maxScore(self, nums: List[int]) -> int:
        nums.sort(reverse=True)
        ans = 0
        n = len(nums)
        prefix = [0] * n
        prefix[0] = nums[0]
        if prefix[0] > 0:
            ans = 1
        else:
            return 0
        for i in range(1,n):
            prefix[i] = prefix[i-1] + nums[i]
            if prefix[i] > 0:
                ans += 1
            else:
                break

        return ans
  • 时间复杂度:O(n+logn)
  • 空间复杂度:O(n)
(14)三角形的最大周长(976)

在这里插入图片描述

  • 时间复杂度:O(n+nlogn)
  • 空间复杂度:O(1)
(15)减小和重新排列数组后的最大元素(1856)

在这里插入图片描述

class Solution:
    def maximumElementAfterDecrementingAndRearranging(self, arr: List[int]) -> int:
        arr.sort()
        arr[0] = 1
        n = len(arr)
        for i in range(1,n):
            if abs(arr[i] - arr[i-1]) > 1:
                arr[i] = arr[i - 1] + 1
        return max(arr)
  • 时间复杂度:O(n+nlogn)
  • 空间复杂度:O(1)
(16)使数组唯一的最小增量(945)【值得刷】

在这里插入图片描述

  • 情况一:num >= next_free
    说明当前num已经是唯一的了,不需要增加,更新next_free为num+1,表示下一个可用数字为num+1
  • 情况二:num < next_free
    说明当前num已被使用过,需要将其增加到next_free,更新next_free为next_free+1
class Solution:
    def minIncrementForUnique(self, nums: List[int]) -> int:
        nums.sort()
        next_free = nums[0]
        ans = 0
        for num in nums:
            if num < next_free:
                ans += next_free - num
                next_free += 1
            else:
                next_free = num + 1
        return ans
  • 时间复杂度:O(n+nlogn)
  • 空间复杂度:O(1)
(17)高度互不相同的最大塔高和(3301)

相当舒服,跟上一道题一样的思路
在这里插入图片描述

class Solution:
    def maximumTotalSum(self, maximumHeight: List[int]) -> int:
        maximumHeight.sort(reverse=True)
        next_free = maximumHeight[0]
        for i in range(len(maximumHeight)):
            if maximumHeight[i] > next_free:
                maximumHeight[i] = next_free
                next_free -= 1
            else:
                next_free =  maximumHeight[i] - 1
        if next_free < 0:
            return -1
        return sum(maximumHeight)
         
  • 时间复杂度:O(n+nlogn)
  • 空间复杂度:O(1)
(18)字符频次唯一的最小删除次数(1647)

在这里插入图片描述

class Solution:
    def minDeletions(self, s: str) -> int:
        kinds = collections.Counter(s)
        kinds = sorted(kinds.items(),key=lambda x:x[1],reverse=True) 
        next_free = kinds[0][1]
        ans = 0
        for i in range(len(kinds)):
            if kinds[i][1] > next_free:
                if next_free >= 1:
                    ans += kinds[i][1] - next_free
                    next_free -= 1
                else:
                    ans += kinds[i][1]                 
            else:
                next_free = kinds[i][1] - 1
          
        return ans               
  • 时间复杂度:O(n+nlogn)
  • 空间复杂度:O(n)
(19)找到最大周长的多边形(2971)

在这里插入图片描述

class Solution:
    def largestPerimeter(self, nums: List[int]) -> int:
        nums.sort(reverse=True)
        n = len(nums)
        cur_edge = sum(nums)
        ans = 0
        if n == 3:
            if nums[0] >= sum(nums[1:]):
                return -1
            else:
                return sum(nums)
        for i in range(n-1):#7,5,1,1
            cur_edge -= nums[i]
            if cur_edge <= nums[i]:
                continue
            else:
                ans = cur_edge + nums[i]
                break
        if ans <= 0:
            ans = -1
        return ans         
  • 时间复杂度:O(n+nlogn)
  • 空间复杂度:O(1)
(20)拆分成最多数目的正偶数之和

在这里插入图片描述

class Solution:
    def maximumEvenSplit(self, finalSum: int) -> List[int]:
        if finalSum % 2:
            return []
        i = 2
        ans = []
        while i <= finalSum:
            ans.append(i)
            finalSum -= i
            i += 2
        ans[-1] += finalSum
        return ans

在这里插入图片描述

2、单序列配对

从最小/最大的元素开始贪心。

(1)打折购买糖果的最小开销(2144)

在这里插入图片描述

class Solution:
    def minimumCost(self, cost: List[int]) -> int:
        n = len(cost)
        if n == 2:
            return sum(cost)
        cost.sort(reverse=True)
        ans = 0
        while n >= 3:
            ans += cost[0] + cost[1]
            cost = cost[3:]
            n = len(cost)
        if n > 0:
            ans += sum(cost)
        return ans
  • 时间复杂度:O(n+nlogn)
  • 空间复杂度:O(1)
(2)数组拆分(561)

在这里插入图片描述

class Solution:
    def arrayPairSum(self, nums: List[int]) -> int:
        n = len(nums)
        pairs_num = n // 2
        nums.sort(reverse=True)
        ans = 0
        i = 1
        while i < n:
            ans += nums[i]
            i += 2
        return ans
  • 时间复杂度:O(n+nlogn)
  • 空间复杂度:O(1)
(3)数组中最大数对和的最小值(1877)

在这里插入图片描述

class Solution:
    def minPairSum(self, nums: List[int]) -> int:
        #最大的数和最小的数组一对
        ans = 0
        nums.sort()
        n = len(nums)
        left,right = 0,n-1
        while left < right:
            ans = max(ans,nums[left] + nums[right])
            left += 1
            right -= 1
        return ans
  • 时间复杂度:O(n+nlogn)
  • 空间复杂度:O(1)
(4)救生艇(881)

在这里插入图片描述

class Solution:
    def numRescueBoats(self, people: List[int], limit: int) -> int:
        n = len(people)
        if n == 2 and sum(people) <= limit:
            return 1
        people.sort()
        def dfs(left:int,right:int)->int:
            ans = 0
            if left > right:
                return ans
            if people[left] + people[right] <= limit:
                ans += 1
                return ans + dfs(left+1,right-1)
            else:
                ans += 1
                return ans + dfs(left,right-1)
            return ans
        return dfs(0,n-1)
  • 时间复杂度:O(n+nlogn)
  • 空间复杂度:O(1)
(5)最大化数组的伟大值【值得刷】

在这里插入图片描述

class Solution:
    def maximizeGreatness(self, nums: List[int]) -> int:
        nums.sort()
        i = 0
        for num in nums:
            if num > nums[i]:
                i += 1
        return i
  • 时间复杂度:O(nlogn)
  • 空间复杂度:O(1)
(6)求出最多标记下标(2576)

在这里插入图片描述

class Solution:
    def maxNumOfMarkedIndices(self, nums: List[int]) -> int:
        nums.sort()
        n = len(nums)
        count = 0
        left,right = 0, n // 2
        while left < n // 2 and right < n:
            if 2 * nums[left] <= nums[right]:
                count += 1
                left += 1
                right += 1
            else:
                right += 1
        return 2 * count
  • 时间复杂度:O(n+nlogn)
  • 空间复杂度:O(1)

3、双序列配对

从最小/最大的元素开始贪心。

(1)使每位学生都有座位的最少移动次数(2037)

在这里插入图片描述

class Solution:
    def minMovesToSeat(self, seats: List[int], students: List[int]) -> int:
        seats.sort()
        students.sort()
        n = len(seats)
        ans = 0
        for i in range(n):
            ans += abs(seats[i] - students[i])
        return ans
  • 时间复杂度:O(nlogn)
  • 空间复杂度:O(1)
(2)分发饼干(455)

在这里插入图片描述

class Solution:
    def findContentChildren(self, g: List[int], s: List[int]) -> int:
        g.sort()
        s.sort()
        n = len(s)
        i = 0
        for j in range(n):
            if i < len(g) and g[i] <= s[j]:
                i += 1
        return i
  • 时间复杂度:O(n+nlogn)
  • 空间复杂度:O(1)
(3)运动员和训练师的最大匹配数

在这里插入图片描述

class Solution:
    def matchPlayersAndTrainers(self, players: List[int], trainers: List[int]) -> int:
        players.sort()
        trainers.sort()
        i = 0
        n = len(trainers)
        for j in range(n):
            if i < len(players) and players[i] <= trainers[j]:
                i += 1
        return i
  • 时间复杂度:O(n+nlogn)
  • 空间复杂度:O(1)
(4)检查一个字符串是否可以打破另一个字符串(1433)

在这里插入图片描述

class Solution:
    def checkIfCanBreak(self, s1: str, s2: str) -> bool:
        s1_list = sorted(s1)
        s2_list = sorted(s2)
        n = len(s1)
        flag1 = 1
        flag2 = 1
        for i in range(n):
            if s1_list[i] < s2_list[i]:
                flag1 = 0
            if s2_list[i] < s1_list[i]:
                flag2 = 0
        return True if flag1 or flag2 else False
  • 时间复杂度:O(n+nlogn)
  • 空间复杂度:O(1)
(5)优势洗牌(870)【值得刷】

在这里插入图片描述

class Solution:
    def advantageCount(self, nums1: List[int], nums2: List[int]) -> List[int]:
        location = defaultdict(list)
        n = len(nums1)
        for i in range(n):
            location[nums2[i]].append(i)
        ans = [0] * n

        nums1.sort()
        nums2.sort()

        i,j = 0,n-1
        remaining = []

        for num in nums1:
            if num > nums2[i]:
                ans[location[nums2[i]].pop()] = num
                i += 1
            else:
                remaining.append(num)

        for num in remaining:
            ans[location[nums2[j]].pop()] = num
            j -= 1
        return ans
  • 时间复杂度:O(n+nlogn)
  • 空间复杂度:O(n)
(6)安排工作以达到最大收益(826)【值得刷】

在这里插入图片描述

class Solution:
    def maxProfitAssignment(self, difficulty: List[int], profit: List[int], worker: List[int]) -> int:
        jobs = sorted(zip(difficulty,profit))
        worker.sort()
        ans = j = max_profit = 0
        for w in worker:
            while j < len(jobs) and jobs[j][0] <= w:
                max_profit = max(max_profit,jobs[j][1])
                j += 1
            ans += max_profit
        return ans
  • 时间复杂度:O(nlogn+mlogm),n为difficulty长度,m为worker长度
  • 空间复杂度:O(n)
(7)使数组相似的最少操作次数(2449)【值得刷】

在这里插入图片描述
这个区分偶数和奇数的方法真的太妙了!!!

def f(nums:List[int]):
    for i,x in enumerate(nums):
        if x % 2:
            nums[i] = -x
    nums.sort()
class Solution:
    def makeSimilar(self, nums: List[int], target: List[int]) -> int:
        f(nums)
        f(target)
        return sum(abs(x-y) for x,y in zip(nums,target)) // 4
  • 时间复杂度:O(nlogn)
  • 空间复杂度:O(1)
(8)装包裹的最小浪费空间【值得刷】

在这里插入图片描述
贪心+前缀和+二分查找

class Solution:
    def minWastedSpace(self, packages: List[int], boxes: List[List[int]]) -> int:
        MOD = 10 ** 9 + 7
        packages.sort()
        n = len(packages)
        prefix_sum = [0] * (n+1)
        for i in range(n):
            prefix_sum[i + 1] = prefix_sum[i] + packages[i]
        min_waste = float('inf')

        for box in boxes:
            box.sort()
            if box[-1] <packages[-1]:
                continue
            waste = 0
            prev = 0
            for size in box:
                idx = bisect.bisect_right(packages,size,prev)
                total = prefix_sum[idx] - prefix_sum[prev]
                waste += (idx-prev) * size - total
                prev = idx
                if prev == n:
                    break
            if prev == n:
                min_waste = min(min_waste,waste)
        return min_waste % MOD if min_waste != float('inf') else -1
(9)重排水果(2561)【值得刷】

在这里插入图片描述
这不经过训练谁想得到==

class Solution:
    def minCost(self, basket1: List[int], basket2: List[int]) -> int:
        count1 = collections.Counter(basket1)
        count2 = collections.Counter(basket2)
        combined = count1 + count2
        for k,v in combined.items():
            if v % 2:
                return -1
        
        extra1,extra2 = [],[]
        for item in combined:
            diff = count1[item] - count2[item]
            if diff > 0:
                extra1.extend([item] * (diff // 2))
            elif diff <0:
                extra2.extend([item] * (-diff // 2))
        
        if len(extra1) != len(extra2):
            return -1
        extra1.sort()
        extra2.sort(reverse=True)
        min_element = min(basket1 + basket2)
        ans = 0
        for x,y in zip(extra1,extra2):
            ans += min(x,y,min_element * 2)
        return ans
  • 时间复杂度:O(nlogn)
  • 空间复杂度:O(n)

4、从最左/最右开始贪心

对于无法排序的题目,尝试从左到右/从右到左贪心。思考第一个数/最后一个数的贪心策略,把n个数的原问题转换成n-1个数(或更少)的子问题。

(1)使二进制数组全部等于1的最少操作次数I(3191)

在这里插入图片描述

class Solution:
    def minOperations(self, nums: List[int]) -> int:
        ans = 0
        for i in range(len(nums) - 2):
            if nums[i] == 0:
                nums[i+1] ^= 1
                nums[i+2] ^= 1
                ans += 1
        return ans if nums[-2] and nums[-1] else -1
  • 时间复杂度:O(nk),n为nums长度,k=3为每次操作反转的元素个数
  • 空间复杂度:O(1)
(2)最少操作使数组递增(1827)

在这里插入图片描述

class Solution:
    def minOperations(self, nums: List[int]) -> int:
        n = len(nums)
        if n == 1:
            return 0
        ans = 0
        for i in range(n-1):
            if nums[i] >= nums[i+1]:
                ans += nums[i] - nums[i+1] + 1
                nums[i + 1] = nums[i] + 1
        return ans            
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)
(3)转换字符串的最少操作次数(2027)

在这里插入图片描述
这道题和3191的区别就是只会从’X’变成’0’,而不会从’0’变成’X’,所以需要注意最后两位的情况

class Solution:
    def minimumMoves(self, s: str) -> int:
        ans = 0
        n = len(s)
        ch = list(s)
        for i in range(n-2):
            if ch[i] == 'X':
                ch[i+1] = '0'
                ch[i+2] = '0'
                ans += 1

        if ch[-1] == 'X' or ch[-2] == 'X':
            ans += 1
        return ans
  • 时间复杂度:O(n)
  • 空间复杂度:O(n)
(4)种花问题(605)

在这里插入图片描述

class Solution:
    def canPlaceFlowers(self, flowerbed: List[int], n: int) -> bool:
        flowerbed = [0] + flowerbed + [0]
        m = len(flowerbed)
        for i in range(1,m-1):
            if flowerbed[i] == 0 and flowerbed[i-1] == 0 and flowerbed[i + 1] == 0:
                flowerbed[i] = 1
                n -= 1
        return n <= 0
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)
(5)覆盖所有点的最少矩形数目(3111)

在这里插入图片描述

class Solution:
    def minRectanglesToCoverPoints(self, points: List[List[int]], w: int) -> int:
        points = sorted(points,key=lambda x:x[0])
        n = len(points)
        i = 0
        ans = 1
        for j in range(1,n):
            if points[i][0] + w >= points[j][0]:
                continue
            else:
                ans += 1
                i = j
        return ans
     
  • 时间复杂度:O(n+nlogn)
  • 空间复杂度:O(1)
(6)覆盖所有点的最少矩形数目(3111)

在这里插入图片描述

class Solution:
    def minRectanglesToCoverPoints(self, points: List[List[int]], w: int) -> int:
        points = sorted(points,key=lambda x:x[0])
        n = len(points)
        i = 0
        ans = 1
        for j in range(1,n):
            if points[i][0] + w >= points[j][0]:
                continue
            else:
                ans += 1
                i = j
        return ans      
  • 时间复杂度:O(n+nlogn)
  • 空间复杂度:O(1)
(7)消除相邻近似相等字符

在这里插入图片描述

class Solution:
    def removeAlmostEqualCharacters(self, word: str) -> int:
        ans = 0
        i = 1
        n = len(word)
        while i < n:
            if abs(ord(word[i]) - ord(word[i-1])) <= 1:
                ans += 1
                i += 2
            else:
                i += 1
  • 时间复杂度:O(N)
  • 空间复杂度:O(1)
(8)使二进制数组全部等于1的最少操作次数II(3192)

在这里插入图片描述

class Solution:
    def minOperations(self, nums: List[int]) -> int:
        k = 0
        for x in nums:
            if (x == 0 and k % 2) or (x == 1 and k % 2 == 0):
                continue
            else:
                k += 1
                x ^= 1
        return k

  • 时间复杂度:O(n)
  • 空间复杂度:O(1)
(9)合并后数组中的最大元素(2789)

在这里插入图片描述

class Solution:
    def maxArrayValue(self, nums: List[int]) -> int:
        s = nums[-1]
        n = len(nums)
        for i in range(n-2,-1,-1):
            s = s + nums[i] if nums[i] <= s else nums[i]
        return s
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)
(10)最少得后缀翻转次数(1529)

在这里插入图片描述

class Solution:
    def minFlips(self, target: str) -> int:
        ans = 0
        flip = 0 #0表示未翻转,1表示已翻转
        for ch in target:
            cur = flip % 2 #当前s的状态
            tar = int(ch)
            if cur != tar:
                flip += 1
                ans += 1
        return ans
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)
(11)递减元素使数组呈锯齿状

在这里插入图片描述
为了使操作次数尽量小,nums[i]不断减小到比左右相邻数字都小,就立刻停止。所以nums[i]要修改成m=min(nums[i-1],nums[i+1])-1,修改次数为nums[i]-m,如果nums[i]本来就不超过m,无需修改。
因此nums[i]的修改次数为
m a x ( n u m s [ i ] − m i n ( n u m s [ i − 1 ] , n u m s [ i + 1 ] ) + 1 , 0 ) max(nums[i]-min(nums[i-1],nums[i+1])+1,0) max(nums[i]min(nums[i1],nums[i+1])+1,0)
若i-1或i+1下标越界,则对应的数字视作无穷大。
最后,把偶数和奇数下标对应的修改次数分别累加,结果分别设为s0和s1,那么答案就是min(s0,s1)

class Solution:
    def movesToMakeZigzag(self, nums: List[int]) -> int:
        s = [0] * 2
        for i,x in enumerate(nums):
            left = nums[i-1] if i-1 >= 0 else inf
            right = nums[i+1] if i+1 < len(nums) else inf
            s[i % 2] += max(nums[i] - min(left,right)+1,0)
        return min(s)
  • 时间复杂度:O(N)
  • 空间复杂度:O(1)
(12)将 1 移动到末尾的最大操作次数(3228)

在这里插入图片描述

class Solution:
    def maxOperations(self, s: str) -> int:
        ans = cnt = 0
        for i,c in enumerate(s):
            if c == '1':
                cnt += 1
            elif i and s[i-1] == '1':
                ans += cnt
        return ans     
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)
(13)喂食仓鼠的最小食物桶数(2086)

在这里插入图片描述

class Solution:
    def minimumBuckets(self, hamsters: str) -> int:
        n = len(hamsters)
        i,ans = 0,0
        while i < n:
            if hamsters[i] == 'H':
                if i+1 < n and hamsters[i+1] == '.':
                    ans += 1
                    i += 2
                elif i-1 >= 0 and hamsters[i-1] =='.':
                    ans += 1
                else:
                    return -1
            i += 1
        return ans
        
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)
(14)将整数减少到零需要的最少操作数(2571)

在这里插入图片描述
把n看成二进制数,那么更高位的比特1是会收到更低位的比特1的加减影响的,但是最小的比特1没有这个约束。
那么考虑优先消除最小的比特1,设它对应的数字为lowbit。
消除方法只能是加上lowbit或减去lowbit。
贪心策略:若有多个连续1,则采用加法更优,可一次消除多个1;否则对于单个1,减法更优。

class Solution:
    def minOperations(self, n: int) -> int:
        ans = 1
        while n & (n-1):
            lb = n & -n
            if n & (lb << 1):
                n += lb
            else:
                n -= lb
            ans += 1
        return ans       
  • ans:初始化为1,因为若n已经是2的幂,则只需一次操作。

  • while n & (n-1):用来检查n是否是2的幂,因为若n是2的幂,n的二进制形式只包含一个1,在这种情况下,n&(n-1)的结果为0。

  • lb = n & -n:提取n的最低位的1,即n二进制形式中的最右边的1位。

  • n & (lb << 1):将lb左移一位,若n在该位置也有1,则n有连续的1位。

  • 时间复杂度:O(n)

  • 空间复杂度:O(1)

(15)使所有字符相等的最小成本(1791)

在这里插入图片描述

class Solution:
    def minimumCost(self, s: str) -> int:
        n = len(s)
        ans = 0
        for i in range(1,n):
            if s[i] != s[i-1]:
                ans += min(i,n-i)
        return ans      
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)
(16)使二叉树所有路径值相等的最小代价(2673)

在这里插入图片描述

class Solution:
    def minIncrements(self, n: int, cost: List[int]) -> int:
        ans = 0
        for i in range(n-2,0,-2):
            ans += abs(cost[i] - cost[i+1])
            cost[i // 2] += max(cost[i],cost[i+1])
        return ans
  
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

5、划分型贪心

把数组/字符串划分成满足要求的若干段,最小化/最大化划分的段数。

思考方法同上,尝试从左到右/从右到左贪心。

6、先枚举,再贪心

枚举题目的其中一个变量,将其视作已知条件,然后在此基础上贪心。

也可以枚举答案,检查是否可以满足要求。(类似二分答案)

(1)枚举最后袋子中魔法豆的数目(2171)【值得刷】

在这里插入图片描述

拿出魔法豆+剩余魔法豆=初始魔法豆之和,可以考虑最多剩余多少魔法豆,从而计算出最少能拿出多少个魔法豆。
在这里插入图片描述

class Solution:
    def minimumRemoval(self, beans: List[int]) -> int:
        beans.sort()
        return sum(beans) - max((len(beans) - i) * v for i,v in enumerate(beans))   
  • 时间复杂度:O(nlogn)
  • 空间复杂度:O(1)
(2)成为K特殊字符串需要删除的最少字符数(3085)

在这里插入图片描述
统计word中每个字母的出现次数,记录一个数组cnt中。
枚举i作为出现次数最小的字母,为保留尽量多的字母,字母i肯定不需要删除。此外,出现次数最多的字母,其出现次数不能超过cnt[i]+k。
分类讨论:

  • 出现次数小于cnt[i]的字母,全部删除
  • 出现次数大于等于cnt[i]的字母j,保留min(cnt[j],cnt[i]+k)个。累加保留的字母个数,更新最多保留的字母个数maxSave的最大值。
class Solution:
    def minimumDeletions(self, word: str, k: int) -> int:
        cnt = sorted(Counter(word).values())
        max_save = max(sum(min(c,base + k) for c in cnt[i:]) for i,base in enumerate(cnt))
        return len(word) - max_save     

在这里插入图片描述

7、交换论证法

  • 对于题目,猜想按照某种顺序处理数据,可以得到最优解;
  • 交换顺序中的两个元素ai和aj,计算交换后的答案;
  • 对比交换前后的答案。如果交换后,答案没有变得更优,则说明猜想成立。

也可以不用猜想,而是计算先ai后aj和先aj后ai对应的答案,通过比较两个答案谁更优,来确定按照何种顺序处理数据。

(1)切蛋糕的最小总开销II(3219)

在这里插入图片描述
在这里插入图片描述

每条水平线和垂直线,最终都要全部切完。

  • 水平线(横切)开销horizontalCut[i]对答案的贡献,等于horizontalCut[i]乘以横切次数(经过多少块蛋糕),即在此之前的竖切次数+1。
  • 垂直线(竖切)开销verticalCut[i]对答案的贡献,等于verticalCut[i]乘以竖切次数(经过多少块蛋糕),即在此之前的横切次数+1。

以示例1为例,其操作序列为
竖切 0 ,横切 0 ,横切 1 竖切0,横切0,横切1 竖切0,横切0,横切1
最小总开销为
v e r t i c a l C u t [ 0 ] ∗ 1 + h o r i z o n t a l C u t [ 0 ] ∗ 2 + h o r i z o n t a l C u t [ 1 ] ∗ 2 verticalCut[0] * 1 + horizontalCut[0] * 2 + horizontalCut[1]*2 verticalCut[0]1+horizontalCut[0]2+horizontalCut[1]2
设横切的开销为h,若先横切,设需要横切cntH次。
设竖切的开销为v,若先竖切,设需要竖切cntV次。

  • 先横切,再竖切,那么竖切的次数(这一刀经过蛋糕的次数)要多1,开销为
    h ∗ c n t H + v ∗ ( c n t V + 1 ) h * cntH + v * (cntV+1) hcntH+v(cntV+1)
  • 先竖切,再横切,那么横切的次数(这一刀经过蛋糕的次数)要多1,开销为
    v ∗ c n t V + h ∗ ( c n t H + 1 ) v * cntV + h * (cntH+1) vcntV+h(cntH+1)
    若先横再竖开销更小,则有
    h ∗ c n t H + v ∗ ( c n t V + 1 ) < v ∗ c n t V + h ∗ ( c n t H + 1 ) h * cntH + v * (cntV+1) < v * cntV + h * (cntH+1) hcntH+v(cntV+1)<vcntV+h(cntH+1)
    化简为
    h > v h > v h>v
    这意味着,谁的开销更大,就先切谁,并且这个先后顺序与cntH和cntV无关。
    做法:
  • 把horizontalCut和verticalCut从大到小排序;
  • 初始化cntH=1,cntV=1,i=0,j=0;
  • 双指针遍历horizontalCut和verticalCut;
  • 若horizontalCut[i]>verticalCut[j],则优先横切,把horizontalCut[i] * cntH假如答案,i+1,然后需要竖切的次数增加,即cntV+1;否则优先竖切,把verticalCut[j]*cntV加入答案,j+1,然后需要横切的次数增加,即cntH+1.
  • 循环直到两个数组都遍历完;
  • 返回答案。
class Solution:
    def minimumCost(self, m: int, n: int, horizontalCut: List[int], verticalCut: List[int]) -> int:
        horizontalCut.sort(reverse=True)
        verticalCut.sort(reverse=True)
        ans = 0
        cntH,cntV = 1,1
        i,j = 0,0
        while i < m - 1 or j < n - 1:
            if j == n - 1 or i < m - 1 and horizontalCut[i] > verticalCut[j]:
                ans += horizontalCut[i] * cntH
                i += 1
                cntV += 1
            else:
                ans += verticalCut[j] * cntV
                j += 1
                cntH += 1
        return ans
        
  • 时间复杂度:O(mlogm+nlogn)
  • 空间复杂度:O(1)
(2)对Bob造成的最少伤害(3273)【值得刷】

在这里插入图片描述
首先,一直攻击同一个敌人,相比来回攻击多个敌人(雨露均沾)更好,因为这样我们被敌人攻击的次数更少。
从特殊到一般,如果只有两个敌人A和B,我们应该先攻击谁?
消灭A需要攻击的次数:
在这里插入图片描述
同理可得消灭B需要的攻击次数,记作kB。
若先消灭A,再消灭B,那么受到的伤害总和为
k A × d a m a g e A + ( k A + k B ) × d a m a g e B k_A \times damage_A + (k_A+k_B) \times damage_B kA×damageA+(kA+kB)×damageB
若先消灭B,再消灭A,那么受到的伤害总和为
k B × d a m a g e B + ( k A + k B ) × d a m a g e A k_B \times damage_B + (k_A+k_B) \times damage_A kB×damageB+(kA+kB)×damageA
如果先消灭A,再消灭B更好,则有

k A × d a m a g e A + ( k A + k B ) × d a m a g e B < k B × d a m a g e B + ( k A + k B ) × d a m a g e A k_A \times damage_A + (k_A+k_B) \times damage_B < k_B \times damage_B + (k_A+k_B) \times damage_A kA×damageA+(kA+kB)×damageB<kB×damageB+(kA+kB)×damageA

化简得
k A × d a m a g e B < k B × d a m g e A k_A \times damage_B < k_B \times damge_A kA×damageB<kB×damgeA
k A / d a m g e A < k B / d a m a g e B k_A / damge_A < k_B / damage_B kA/damgeA<kB/damageB
即优先消灭k/damage更小的敌人。

class Solution:
    def minDamage(self, power: int, damage: List[int], health: List[int]) -> int:
        n = len(damage)
        for i in range(n):
            health[i] = (health[i] - 1) // power + 1
        ans = s = 0

        for i in sorted(range(n),key=lambda i:health[i]/damage[i]):
            s += health[i]
            ans += s * damage[i]

        return ans
  • 时间复杂度:O(nlogn)
  • 空间复杂度:O(1)
(3)全部开花的最早一天(2136)

在这里插入图片描述

(4)最大数(179)

在这里插入图片描述
这里的排序使用了 key=lambda x: x * 10,表示将每个字符串扩展为自身重复 10 次,然后按降序排序。

具体原因是,我们需要确保两数之间组合的最大效果。例如,对于 3 和 30,我们比较 330 和 303,显然 330 更大,因此 3 应该在 30 前。

通过 x * 10 将每个数字字符串扩展 10 次,例如 “3” * 10 = “3333333333”,“30” * 10 = “3030303030”,这样做可以确保在字符串组合时按正确顺序排列。

class Solution:
    def largestNumber(self, nums: List[int]) -> str:
        nums = list(map(str,nums))
        nums.sort(key=lambda x:x*10,reverse=True)
        
        ans = ''.join(nums)

        return ans if ans[0] != '0' else '0'
  • 时间复杂度:O(nlogn)
  • 空间复杂度O(n)
(5)连接二进制表示可形成的最大数值(3309)

在这里插入图片描述

class Solution:
    def maxGoodNumber(self, nums: List[int]) -> int:
        s = []
        for x in nums:
            x = format(x,'b')
            s.append(x)
        s.sort(key=lambda x:x * 10,reverse=True)
        ans = ''.join(s)
        return int(ans,2)
  • 时间复杂度:O(nlogn)
  • 空间复杂度:O(n)

8、相邻不同

给定正整数数组,每次操作,把数组中的两个数各减一,并去掉变成0的数。目标:使最后剩下的数最小,或最大化操作次数。
由于每次操作的都是两个下标不同的数,把这些下标按顺序拼接,可以构造出一个相邻元素不同的序列。

(1)重构字符串

在这里插入图片描述

class Solution:
    def reorganizeString(self, s: str) -> str:
        kinds = collections.Counter(s)
        kinds = sorted(kinds.items(),key=lambda x:x[1],reverse=True)
        max_freq = kinds[0][1]
        if max_freq > (len(s) + 1) // 2:
            return ""

        ans = [""] * len(s)
        index = 0
        for char,freq in kinds:
            for _ in range(freq):
                ans[index] = char
                index += 2
                if index >= len(s):
                    index = 1
        return "".join(ans)
  • 时间复杂度:O(nlogn)
  • 空间复杂度:O(n)
(2)删除数对后的最小数组长度(2856)

在这里插入图片描述
假设x出现次数最多,其出现次数为maxCnt。
分类讨论:

  • 若maxCnt * 2 > n,其余所有n-maxCnt个数都要与x消除,所以最后剩下maxCnt*2-n个数;
  • 若maxCnt*2 ≤ n且n为偶数,那么可以把其余数消除至剩下maxCnt个数,然后再和x消除,最后剩下0个数;
  • 如果maxCnt*2≤n且n为奇数,同上,最后剩下1个数。
    由于nums是有序的,如果maxCnt超过数组长度的一半,那么nums[n/2]一定是出现次数最多的那个数。
    bisect_left返回x第一次出现的索引,bisect_right返回x最后出现位置的下一个索引。
class Solution:
    def minLengthAfterRemovals(self, nums: List[int]) -> int:
        n = len(nums)
        x = nums[n // 2]
        max_cnt = bisect_right(nums,x) - bisect_left(nums,x)
        return max(max_cnt * 2 - n,n % 2)
        
  • 时间复杂度:O(N)
  • 空间复杂度:O(1)
(3)你可以工作的最大周数(1953)

在这里插入图片描述

class Solution:
    def numberOfWeeks(self, milestones: List[int]) -> int:
        total = sum(milestones)
        max_stone = max(milestones)
        if total - max_stone >= max_stone:
            return total
        else:
            return 2 * (total - max_stone) + 1
  • 时间复杂度:O(N)
  • 空间复杂度:O(1)
(4)不含 AAA 或 BBB 的字符串(984)

在这里插入图片描述

class Solution:
    def strWithout3a3b(self, a: int, b: int) -> str:
        res = []
        while a > 0 or b > 0:
            if len(res) >= 2 and res[-1] == res[-2]:
                add_a = res[-1] == 'b'
            else:
                add_a = a >= b
            if add_a:
                res.append('a')
                a -= 1
            else:
                res.append('b')
                b -= 1
        return "".join(res)
  • 时间复杂度:O(n)
  • 空间复杂度:O(n)
(5)最长快乐字符串(1405)

在这里插入图片描述

  • 时间复杂度:O(N)
  • 空间复杂度:O(n)

9、反悔贪心

一定要用到堆。

(1)魔塔游戏(LCP30)

在这里插入图片描述
首先,若nums的元素和小于0,那么即使把所有负数都移到末尾,也无法访问所有房间,返回-1。
否则遍历数组,能加血就尽管加血,要扣血就直接扣血,但如果血量小于1,我们就反悔:从前面的扣血中,拿出一个扣血量最大的数(最小的负数),移到数组的末尾,把之前扣掉的血重新加回来。
具体来说:
①初始化血量hp=1
②从左到右遍历数组,把小于0的数丢到一个小根堆中。
3.遍历的同时,把nums[i]加到hp中。如果hp<1,则弹出堆顶,hp减去堆顶,相当于把之前扣掉的血重新加回来。同时把调整次数+1。如果hp<1,那么必然是由当前这个小于0的nums[i]导致的,这一保证了此时堆不为空,二保证了hp减去堆顶后必然可以恢复成整数,因为堆顶不会比nums[i]还大。

class Solution:
    def magicTower(self, nums: List[int]) -> int:
        if sum(nums) < 0:
            return -1
        hp = 1
        h = []
        ans = 0
        for x in nums:
            if x < 0:
                heappush(h,x)
            hp += x
            if hp < 1:
                hp -= heappop(h)
                ans += 1
        return ans

  • 时间复杂度:O(nlogn)
  • 空间复杂度:O(n)
(2)可以到达的最远建筑(1642)

在这里插入图片描述
在这里插入图片描述

class Solution:
    def furthestBuilding(self, heights: List[int], bricks: int, ladders: int) -> int:
        n = len(heights)
        h = []
        for i in range(1,n):
            diff = heights[i]-heights[i-1]
            if diff <= 0:
                continue
            heappush(h,diff)
            if len(h) > ladders:
                bricks -= heappop(h)
            if bricks < 0:
                return i - 1
        return n - 1

  • 时间复杂度:O(nlogn)
  • 空间复杂度:O(n)
(3)课程表III(630)

在这里插入图片描述

class Solution:
    def scheduleCourse(self, courses: List[List[int]]) -> int:
        h = []
        total_time = 0
        courses = sorted(courses,key=lambda x:x[1])
        for duration,lastday in courses:
            heappush(h,-duration)
            total_time += duration
            if total_time > lastday:
                total_time += heappop(h)
        return len(h)
  • 时间复杂度:O(nlogn)
  • 空间复杂度:O(n)
(4)最低加油次数(871)

在这里插入图片描述

class Solution:
    def minRefuelStops(self, target: int, startFuel: int, stations: List[List[int]]) -> int:
        stations.append((target,0))
        ans = 0
        cur_fuel = startFuel
        pre_position = 0
        fuel_heap = []
        for position,fuel in stations:
            cur_fuel -= position - pre_position
            while fuel_heap and cur_fuel < 0:
                cur_fuel -= heappop(fuel_heap)
                ans += 1
            if cur_fuel < 0:
                return -1
            heappush(fuel_heap,-fuel)
            pre_position = position
        return ans
  • 时间复杂度:O(nlogn)
  • 空间复杂度:O(n)
(5)子序列最大优雅度(2813)

在这里插入图片描述

class Solution:
    def findMaximumElegance(self, items: List[List[int]], k: int) -> int:
        items = sorted(items,key=lambda x:x[0],reverse=True)
        vis = set()
        total_profit = ans = 0
        duplicate = []
        for i,(profit,category) in enumerate(items):
            if i < k:
                total_profit += profit
                if category not in vis:
                    vis.add(category)    
                else:
                    duplicate.append(profit)
            else:
                if duplicate and category not in vis:
                    total_profit -= duplicate.pop()
                    total_profit += profit
                    vis.add(category)
            ans = max(ans,total_profit + len(vis) ** 2)
        return ans
  • 时间复杂度:O(nlogn)
  • 空间复杂度:O(n)
(6)标记所有下标的最早秒数 II

在这里插入图片描述

二分答案+反悔贪心

翻译题目
我们有n门课程,每门课程需要nums[i]天来复习。在某些特定的天数(通过changeIndices[i]给定的天数),可以快速复习某门课程。我们要找到一个最小的天数mx,使得所有课程的复习和考试都可以在mx天内完成。

解题思路:

  • 二分查找:
    • 可以使用二分查找来确定最早的天数mx,在这个天数内所有的可成都能完成复习和考试。
    • 通过check(mx)函数,判断是否可以在mx天内完成任务。如果可以,则以为可能的答案更小,我们继续缩小范围;反之,我们需要增加天数。
  • 快速复习和慢速复习
    • 慢速复习:每天复习一门课程
    • 快速复习:在changeIndices[i]对应的天数,可以快速复习某门课程,将其复习时间直接设为0。
  • 小根堆和反悔机制
    • 反悔目的:将原本计划慢速复习的课程,改为快速复习,以节省时间。快速复习会导致某些课程的复习时间被提前完成,腾出更多时间来安排其他课程。
    • 选择较短复习时间的课程:因为它们对总时间的影响较小。
class Solution:
    def earliestSecondToMarkIndices(self, nums: List[int], changeIndices: List[int]) -> int:
        n = len(nums)
        m = len(changeIndices)
        total = n + sum(nums)

        first_t = [-1] * n
        for t in range(m - 1,-1,-1):
            first_t[changeIndices[t] - 1] = t
        
        def check(mx:int)->bool:
            slow = total
            cnt = 0
            h = []

            for t in range(mx - 1,-1,-1):
                i = changeIndices[t] - 1
                v = nums[i]
                if v <= 1 or t != first_t[i]:
                    cnt += 1
                    continue
                if cnt == 0:
                    if not h or h[0] > v:
                        cnt += 1
                        continue
                    slow += heappop(h) + 1
                    cnt += 2
                slow -= v + 1
                cnt -= 1
                heappush(h,v)
            return cnt >= slow

        ans = n + bisect_left(range(n,m+1),True,key=check)
        return -1 if ans > m else ans
  • 时间复杂度:O(mlogmlogn)
  • 空间复杂度:O(n)
(7)最小移动总距离

在这里插入图片描述

记忆化搜索

对机器人和工厂按照位置从小到大排序,那么每个工厂修复的机器人就是连续的一段了。
定义f(i,j)表示用第i个及其右侧的工厂,修理第j个及其右侧的机器人,机器人移动的最小总距离。
枚举第i个工厂修理了k个机器人,则有f(i,j)=min(k)f(i+1,j+k) + cost(i,j,k)
cost(i,j,k)表示第i个工厂修理第j个到第j+k-1个机器人。

class Solution:
    def minimumTotalDistance(self, robot: List[int], factory: List[List[int]]) -> int:
        n = len(robot)
        m = len(factory)
        robot.sort()
        factory = sorted(factory,key=lambda x:x[0])

        @cache
        def f(i:int,j:int)->int:
            if j == n:
                return 0
            if i == m - 1:
                if n - j > factory[i][1]:
                    return inf
                return sum(abs(x - factory[i][0]) for x in robot[j:])
            res = f(i+1,j)
            s,k = 0,1
            while k <= factory[i][1] and j + k - 1 < m:
                s += abs(factory[i][0] - robot[j + k - 1])
                res = min(res,s + f(i + 1,j + k))
                k += 1
            return res
        return f(0,0)
  • 时间复杂度:O(mn²)
  • 空间复杂度:O(mn)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值