Leetcode_4-24

59. 螺旋矩阵 II

中等

给你一个正整数 n ,生成一个包含 1n2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix

示例 1:

img

输入:n = 3
输出:[[1,2,3],[8,9,4],[7,6,5]]

示例 2:

输入:n = 1
输出:[[1]]

提示:

  • 1 <= n <= 20

深搜

class Solution:
    def generateMatrix(self, n: int) -> List[List[int]]:
        mat = [[0 for _ in range(n)] for _ in range(n)]
        directions = [(0, 1), (1, 0), (0, -1), (-1, 0)]  # right, down, left, up
        used = [[False for _ in range(n)] for _ in range(n)]
        
        def dfs(row, col, current, direction):
            mat[row][col] = current
            used[row][col] = True
            
            if current == n * n:
                return
            
            # 尝试以现在的方向移动
            nr, nc = row + directions[direction][0], col + directions[direction][1]
            if 0 <= nr < n and 0 <= nc < n and not used[nr][nc]:
                dfs(nr, nc, current + 1, direction)
            else:
                # 如果碰壁了或者used了按螺旋顺序改变方向 (right -> down -> left -> up -> right...)
                new_direction = (direction + 1) % 4
                nr, nc = row + directions[new_direction][0], col + directions[new_direction][1]
                dfs(nr, nc, current + 1, new_direction)
        
        dfs(0, 0, 1, 0)
        return mat

nextpage

83. 删除排序链表中的重复元素

简单

给定一个已排序的链表的头 head删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表

示例 1:

img

输入:head = [1,1,2]
输出:[1,2]

示例 2:

img

输入:head = [1,1,2,3,3]
输出:[1,2,3]

提示:

  • 链表中节点数目在范围 [0, 300]
  • -100 <= Node.val <= 100
  • 题目数据保证链表已经按升序 排列

我的写法(dummy很重要)

# Definition for singly-linked list.
class ListNode:
     def __init__(self, val=0, next=None):
         self.val = val
         self.next = next

class Solution:
    def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]:
        dummy = ListNode()
        dummy.next = head

        exist_elems = set()
        cur = dummy.next
        pre = dummy

        while cur != None:
            if cur.val in exist_elems:
                pre.next = cur.next
                cur.next = None
                cur = pre
            exist_elems.add(cur.val)
            pre = cur
            cur = cur.next

        return dummy.next


nextpage

82. 删除排序链表中的重复元素 II

中等

给定一个已排序的链表的头 head删除原始链表中所有重复数字的节点,只留下不同的数字 。返回 已排序的链表

示例 1:

img

输入:head = [1,2,3,3,4,4,5]
输出:[1,2,5]

示例 2:

img

输入:head = [1,1,1,2,3]
输出:[2,3]

提示:

  • 链表中节点数目在范围 [0, 300]
  • -100 <= Node.val <= 100
  • 题目数据保证链表已经按升序 排列

我的做法(先遍历一遍确定需要删除的元素再重走一遍)

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]:
        dummy = ListNode()
        dummy.next = head

        exist_elems = set()
        cur = dummy.next
        pre = dummy

        # 统计一遍哪些元素需要删除
        counter = defaultdict(int)
        need_to_del = []
        while cur != None:
            counter[cur.val] += 1
            if counter[cur.val] >= 2:
                need_to_del.append(cur.val)
            cur = cur.next

        # cur和pre需要复位
        cur = dummy.next
        pre = dummy
        while cur != None:
            if cur.val in need_to_del:
                pre.next = cur.next
                cur.next = None
                cur = pre
            pre = cur
            cur = cur.next

        return dummy.next

nextpage

239. 滑动窗口最大值

困难

给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。

返回 滑动窗口中的最大值

示例 1:

输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
解释:
滑动窗口的位置                最大值
---------------               -----
[1  3  -1] -3  5  3  6  7       3
 1 [3  -1  -3] 5  3  6  7       3
 1  3 [-1  -3  5] 3  6  7       5
 1  3  -1 [-3  5  3] 6  7       5
 1  3  -1  -3 [5  3  6] 7       6
 1  3  -1  -3  5 [3  6  7]      7

示例 2:

输入:nums = [1], k = 1
输出:[1]

提示:

  • 1 <= nums.length <= 105
  • -104 <= nums[i] <= 104
  • 1 <= k <= nums.length

最大栈

class Solution:
    def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
        if not nums:
            return []
        
        dq = deque()
        result = []
        
        for i in range(len(nums)):
            # 移除超出窗口范围的元素索引
            while dq and dq[0] < i - k + 1:
                dq.popleft()
            
            # 移除所有小于当前元素的索引
            while dq and nums[dq[-1]] < nums[i]:
                dq.pop()
            
            # 添加当前元素索引
            dq.append(i)
            
            # 当窗口形成时,添加最大值到结果
            if i >= k - 1:
                result.append(nums[dq[0]])
        
        return result

nextpage

454. 四数相加 II

中等

给你四个整数数组 nums1nums2nums3nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足:

  • 0 <= i, j, k, l < n
  • nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0

示例 1:

输入:nums1 = [1,2], nums2 = [-2,-1], nums3 = [-1,2], nums4 = [0,2]
输出:2
解释:
两个元组如下:
1. (0, 0, 0, 1) -> nums1[0] + nums2[0] + nums3[0] + nums4[1] = 1 + (-2) + (-1) + 2 = 0
2. (1, 1, 0, 0) -> nums1[1] + nums2[1] + nums3[0] + nums4[0] = 2 + (-1) + (-1) + 0 = 0

示例 2:

输入:nums1 = [0], nums2 = [0], nums3 = [0], nums4 = [0]
输出:1

提示:

  • n == nums1.length
  • n == nums2.length
  • n == nums3.length
  • n == nums4.length
  • 1 <= n <= 200
  • -228 <= nums1[i], nums2[i], nums3[i], nums4[i] <= 228

双循环+哈希表

from collections import defaultdict

class Solution:
    def fourSumCount(self, nums1: List[int], nums2: List[int], nums3: List[int], nums4: List[int]) -> int:
        sum12 = defaultdict(int)
        ans = 0
        
        # 计算 nums1 和 nums2 的所有可能的和及其频率
        for x in nums1:
            for y in nums2:
                sum12[x + y] += 1
        
        # 遍历 nums3 和 nums4,检查 -(nums3[i] + nums4[j]) 是否在 sum12 中
        for z in nums3:
            for w in nums4:
                target = -(z + w)
                if target in sum12:
                    ans += sum12[target]
        
        return ans

nextpage

560. 和为 K 的子数组

中等

给你一个整数数组 nums 和一个整数 k ,请你统计并返回 该数组中和为 k 的子数组的个数

子数组是数组中元素的连续非空序列。

示例 1:

输入:nums = [1,1,1], k = 2
输出:2

示例 2:

输入:nums = [1,2,3], k = 3
输出:2

提示:

  • 1 <= nums.length <= 2 * 104
  • -1000 <= nums[i] <= 1000
  • -107 <= k <= 107

移动计算

class Solution:
    def subarraySum(self, nums: List[int], k: int) -> int:
        # 暴力求解(超时)
        # pre = [0] + list(accumulate(nums))
        # ans = 0

        # for i in range(len(pre) - 1):
        #     for j in range(i + 1, len(pre)):
        #         if pre[j] - pre[i] == k:
        #             ans += 1

        # return ans

        # 边遍历边计算
        pre_sum = 0
        pre_counter = defaultdict(int)
        pre_counter[0] = 1

        ans = 0
        for num in nums:
            pre_sum += num
            ans += pre_counter[pre_sum - k]
            pre_counter[pre_sum] += 1

        return ans

nextpage

621. 任务调度器

中等

给你一个用字符数组 tasks 表示的 CPU 需要执行的任务列表,用字母 A 到 Z 表示,以及一个冷却时间 n。每个周期或时间间隔允许完成一项任务。任务可以按任何顺序完成,但有一个限制:两个 相同种类 的任务之间必须有长度为 n 的冷却时间。

返回完成所有任务所需要的 最短时间间隔

示例 1:

**输入:**tasks = ["A","A","A","B","B","B"], n = 2

**输出:**8

解释:

在完成任务 A 之后,你必须等待两个间隔。对任务 B 来说也是一样。在第 3 个间隔,A 和 B 都不能完成,所以你需要待命。在第 4 个间隔,由于已经经过了 2 个间隔,你可以再次执行 A 任务。

示例 2:

**输入:**tasks = ["A","C","A","B","D","B"], n = 1

**输出:**6

**解释:**一种可能的序列是:A -> B -> C -> D -> A -> B。

由于冷却间隔为 1,你可以在完成另一个任务后重复执行这个任务。

示例 3:

**输入:**tasks = ["A","A","A","B","B","B"], n = 3

**输出:**10

**解释:**一种可能的序列为:A -> B -> idle -> idle -> A -> B -> idle -> idle -> A -> B。

只有两种任务类型,A 和 B,需要被 3 个间隔分割。这导致重复执行这些任务的间隔当中有两次待命状态。

提示:

  • 1 <= tasks.length <= 104
  • tasks[i] 是大写英文字母
  • 0 <= n <= 100
from collections import Counter
from typing import List

class Solution:
    def leastInterval(self, tasks: List[str], n: int) -> int:
        task_counts = Counter(tasks)
        cooldown = {task: 0 for task in task_counts}
        time = 0
        
        while sum(task_counts.values()) > 0:
            # 选择可执行且剩余次数最多的任务
            selected_task = None
            max_count = -1
            for task in task_counts:
                if cooldown[task] == 0 and task_counts[task] > 0:
                    if task_counts[task] > max_count:
                        max_count = task_counts[task]
                        selected_task = task
            
            if selected_task is not None:
                # 执行选中的任务
                task_counts[selected_task] -= 1
                cooldown[selected_task] = n + 1  # 设置冷却时间(+1是因为当前时间步会减1)
            
            # 更新所有任务的冷却时间
            for task in cooldown:
                if cooldown[task] > 0:
                    cooldown[task] -= 1
            
            time += 1
        
        return time

nextpage

1128. 等价多米诺骨牌对的数量

简单

给你一组多米诺骨牌 dominoes

形式上,dominoes[i] = [a, b]dominoes[j] = [c, d] 等价 当且仅当 (a == cb == d) 或者 (a == db == c) 。即一张骨牌可以通过旋转 0 度或 180 度得到另一张多米诺骨牌。

0 <= i < j < dominoes.length 的前提下,找出满足 dominoes[i]dominoes[j] 等价的骨牌对 (i, j) 的数量。

示例 1:

输入:dominoes = [[1,2],[2,1],[3,4],[5,6]]
输出:1

示例 2:

输入:dominoes = [[1,2],[1,2],[1,1],[1,2],[2,2]]
输出:3

提示:

  • 1 <= dominoes.length <= 4 * 104
  • dominoes[i].length == 2
  • 1 <= dominoes[i][j] <= 9

简便做法

# 暴力做法
# class Solution:
#     def numEquivDominoPairs(self, dominoes: List[List[int]]) -> int:
#         dominoes = list(map(lambda x:sorted(x),dominoes))
#         dd = defaultdict(int)
#         for domino in dominoes:
#             dd[tuple(domino)] += 1

#         ans = 0
#         for key in dd:
#             ans += ((dd[key])*(dd[key] - 1)) // 2
#         return ans

# 一行做法
class Solution:
    def numEquivDominoPairs(self, dominoes: List[List[int]]) -> int:
        return sum(comb(n, 2) for n in Counter(frozenset(_) for _ in dominoes).values())


nextpage

2071. 你可以安排的最多任务数目

困难

给你 n 个任务和 m 个工人。每个任务需要一定的力量值才能完成,需要的力量值保存在下标从 0 开始的整数数组 tasks 中,第 i 个任务需要 tasks[i] 的力量才能完成。每个工人的力量值保存在下标从 0 开始的整数数组 workers 中,第 j 个工人的力量值为 workers[j] 。每个工人只能完成 一个 任务,且力量值需要 大于等于 该任务的力量要求值(即 workers[j] >= tasks[i] )。

除此以外,你还有 pills 个神奇药丸,可以给 一个工人的力量值 增加 strength 。你可以决定给哪些工人使用药丸,但每个工人 最多 只能使用 一片 药丸。

给你下标从 0 开始的整数数组tasksworkers 以及两个整数 pillsstrength ,请你返回 最多 有多少个任务可以被完成。

示例 1:

输入:tasks = [3,2,1], workers = [0,3,3], pills = 1, strength = 1
输出:3
解释:
我们可以按照如下方案安排药丸:
- 给 0 号工人药丸。
- 0 号工人完成任务 2(0 + 1 >= 1)
- 1 号工人完成任务 1(3 >= 2)
- 2 号工人完成任务 0(3 >= 3)

示例 2:

输入:tasks = [5,4], workers = [0,0,0], pills = 1, strength = 5
输出:1
解释:
我们可以按照如下方案安排药丸:
- 给 0 号工人药丸。
- 0 号工人完成任务 0(0 + 5 >= 5)

示例 3:

输入:tasks = [10,15,30], workers = [0,10,10,10,10], pills = 3, strength = 10
输出:2
解释:
我们可以按照如下方案安排药丸:
- 给 0 号和 1 号工人药丸。
- 0 号工人完成任务 0(0 + 10 >= 10)
- 1 号工人完成任务 1(10 + 10 >= 15)

示例 4:

输入:tasks = [5,9,8,5,9], workers = [1,6,4,2,6], pills = 1, strength = 5
输出:3
解释:
我们可以按照如下方案安排药丸:
- 给 2 号工人药丸。
- 1 号工人完成任务 0(6 >= 5)
- 2 号工人完成任务 2(4 + 5 >= 8)
- 4 号工人完成任务 3(6 >= 5)

提示:

  • n == tasks.length
  • m == workers.length
  • 1 <= n, m <= 5 * 104
  • 0 <= pills <= m
  • 0 <= tasks[i], workers[j], strength <= 109

贪心 + 二分搜索

import bisect
from collections import deque

class Solution:
    def maxTaskAssign(self, tasks: List[int], workers: List[int], pills: int, strength: int) -> int:
        tasks.sort()
        workers.sort()
        
        left, right = 0, min(len(tasks), len(workers))
        answer = 0
        
        def is_possible(k):
            # 检查是否能完成前k小的任务
            temp_pills = pills
            dq = deque()
            # 从最强的k个工人开始匹配
            worker_ptr = len(workers) - 1
            # 从大到小检查k个任务
            for i in range(k - 1, -1, -1):
                task = tasks[i]
                # 将能通过药丸覆盖当前任务的工人加入队列
                while worker_ptr >= len(workers) - k and workers[worker_ptr] + strength >= task:
                    dq.appendleft(workers[worker_ptr])
                    worker_ptr -= 1
                if not dq:
                    return False  # 没有工人能匹配
                # 优先不用药丸
                if dq[-1] >= task:
                    dq.pop()
                else:
                    if temp_pills == 0:
                        return False  # 需要用药但无剩余
                    temp_pills -= 1
                    dq.popleft()  # 用药后取最弱的工人
            return True
        
        # 二分搜索最大k
        while left <= right:
            mid = (left + right) // 2
            if is_possible(mid):
                answer = mid
                left = mid + 1
            else:
                right = mid - 1
        return answer

nextpage

2302. 统计得分小于 K 的子数组数目

困难

一个数组的 分数 定义为数组之和 乘以 数组的长度。

  • 比方说,[1, 2, 3, 4, 5] 的分数为 (1 + 2 + 3 + 4 + 5) * 5 = 75

给你一个正整数数组 nums 和一个整数 k ,请你返回 nums 中分数 严格小于 k非空整数子数组数目

子数组 是数组中的一个连续元素序列。

示例 1:

输入:nums = [2,1,4,3,5], k = 10
输出:6
解释:
有 6 个子数组的分数小于 10 :
- [2] 分数为 2 * 1 = 2 。
- [1] 分数为 1 * 1 = 1 。
- [4] 分数为 4 * 1 = 4 。
- [3] 分数为 3 * 1 = 3 。 
- [5] 分数为 5 * 1 = 5 。
- [2,1] 分数为 (2 + 1) * 2 = 6 。
注意,子数组 [1,4] 和 [4,3,5] 不符合要求,因为它们的分数分别为 10 和 36,但我们要求子数组的分数严格小于 10 。

示例 2:

输入:nums = [1,1,1], k = 5
输出:5
解释:
除了 [1,1,1] 以外每个子数组分数都小于 5 。
[1,1,1] 分数为 (1 + 1 + 1) * 3 = 9 ,大于 5 。
所以总共有 5 个子数组得分小于 5 。

前缀和+滑动窗口

class Solution:
    def countSubarrays(self, nums: List[int], k: int) -> int:
        n = len(nums)
        res, total = 0, 0
        i = 0
        for j in range(n):
            total += nums[j]
            while i <= j and total * (j - i + 1) >= k:
                total -= nums[i]
                i += 1
            res += j - i + 1
        return res
        

希尔滑动(但是没有看穿单调性所以复杂度高)

class Solution:
    def countSubarrays(self, nums: List[int], k: int) -> int:     
        pre_sum = [0] + list(accumulate(nums))

        n = len(nums)
        ans = 0
        for t in range(1,n+1):
            for i in range(t, n+1):
                if t * (pre_sum[i] - pre_sum[i - t]) < k:
                    ans += 1

        return ans

nextpage

2444. 统计定界子数组的数目

困难

给你一个整数数组 nums 和两个整数 minK 以及 maxK

nums 的定界子数组是满足下述条件的一个子数组:

  • 子数组中的 最小值 等于 minK
  • 子数组中的 最大值 等于 maxK

返回定界子数组的数目。

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

示例 1:

输入:nums = [1,3,5,2,7,5], minK = 1, maxK = 5
输出:2
解释:定界子数组是 [1,3,5] 和 [1,3,5,2] 。

示例 2:

输入:nums = [1,1,1,1], minK = 1, maxK = 1
输出:10
解释:nums 的每个子数组都是一个定界子数组。共有 10 个子数组。

提示:

  • 2 <= nums.length <= 105
  • 1 <= nums[i], minK, maxK <= 106

滑动窗口

class Solution:
    def countSubarrays(self, nums: List[int], minK: int, maxK: int) -> int:
        res = 0
        border = -1
        last_min = -1
        last_max = -1
        for i, x in enumerate(nums):
            if x < minK or x > maxK:
                border = i
                last_min = -1
                last_max = -1
            if x == minK:
                last_min = i
            if x == maxK:
                last_max = i
            if last_min != -1 and last_max != -1:
                res += min(last_min, last_max) - border
        return res




nextpage

2799. 统计完全子数组的数目

中等

给你一个由 整数组成的数组 nums

如果数组中的某个子数组满足下述条件,则称之为 完全子数组

  • 子数组中 不同 元素的数目等于整个数组不同元素的数目。

返回数组中 完全子数组 的数目。

子数组 是数组中的一个连续非空序列。

示例 1:

输入:nums = [1,3,1,2,2]
输出:4
解释:完全子数组有:[1,3,1,2]、[1,3,1,2,2]、[3,1,2] 和 [3,1,2,2] 。

示例 2:

输入:nums = [5,5,5,5]
输出:10
解释:数组仅由整数 5 组成,所以任意子数组都满足完全子数组的条件。子数组的总数为 10 。

提示:

  • 1 <= nums.length <= 1000
  • 1 <= nums[i] <= 2000

左右指针解法

class Solution:
    def countCompleteSubarrays(self, nums: List[int]) -> int:
        tottal_unique = len(set(nums))
        
        n = len(nums)
        count = 0
        left = 0
        freq = defaultdict(int)
        current_unique = 0

        for right in range(n):
            if freq[nums[right]] == 0:
                current_unique += 1
            freq[nums[right]] += 1

            while current_unique == tottal_unique:
                count += n-right
                freq[nums[left]] -= 1
                if freq[nums[left]] == 0:
                    current_unique -= 1
                left += 1

        return count

nextpage

2845. 统计趣味子数组的数目

中等

给你一个下标从 0 开始的整数数组 nums ,以及整数 modulo 和整数 k

请你找出并统计数组中 趣味子数组 的数目。

如果 子数组 nums[l..r] 满足下述条件,则称其为 趣味子数组

  • 在范围 [l, r] 内,设 cnt 为满足 nums[i] % modulo == k 的索引 i 的数量。并且 cnt % modulo == k

以整数形式表示并返回趣味子数组的数目。

**注意:**子数组是数组中的一个连续非空的元素序列。

示例 1:

输入:nums = [3,2,4], modulo = 2, k = 1
输出:3
解释:在这个示例中,趣味子数组分别是: 
子数组 nums[0..0] ,也就是 [3] 。 
- 在范围 [0, 0] 内,只存在 1 个下标 i = 0 满足 nums[i] % modulo == k 。
- 因此 cnt = 1 ,且 cnt % modulo == k 。
子数组 nums[0..1] ,也就是 [3,2] 。
- 在范围 [0, 1] 内,只存在 1 个下标 i = 0 满足 nums[i] % modulo == k 。
- 因此 cnt = 1 ,且 cnt % modulo == k 。
子数组 nums[0..2] ,也就是 [3,2,4] 。
- 在范围 [0, 2] 内,只存在 1 个下标 i = 0 满足 nums[i] % modulo == k 。
- 因此 cnt = 1 ,且 cnt % modulo == k 。
可以证明不存在其他趣味子数组。因此,答案为 3 。

示例 2:

输入:nums = [3,1,9,6], modulo = 3, k = 0
输出:2
解释:在这个示例中,趣味子数组分别是: 
子数组 nums[0..3] ,也就是 [3,1,9,6] 。
- 在范围 [0, 3] 内,只存在 3 个下标 i = 0, 2, 3 满足 nums[i] % modulo == k 。
- 因此 cnt = 3 ,且 cnt % modulo == k 。
子数组 nums[1..1] ,也就是 [1] 。
- 在范围 [1, 1] 内,不存在下标满足 nums[i] % modulo == k 。
- 因此 cnt = 0 ,且 cnt % modulo == k 。
可以证明不存在其他趣味子数组,因此答案为 2 。

提示:

  • 1 <= nums.length <= 105
  • 1 <= nums[i] <= 109
  • 1 <= modulo <= 109
  • 0 <= k < modulo

前缀和+哈希表

from typing import List
from collections import defaultdict

class Solution:
    def countInterestingSubarrays(self, nums: List[int], modulo: int, k: int) -> int:
        n = len(nums)
        prefix = 0
        count = 0
        # 使用哈希表记录前缀和模modulo的出现次数,初始时prefix[0] = 0出现1次
        prefix_counts = defaultdict(int)
        prefix_counts[0] = 1
        
        for num in nums:
            if num % modulo == k:
                prefix += 1
            # 当前前缀和的模
            current_mod = prefix % modulo
            # 我们需要找 (current_mod - k) % modulo
            target = (current_mod - k) % modulo
            count += prefix_counts.get(target, 0)
            # 更新当前前缀和的模的出现次数
            prefix_counts[current_mod] += 1
        
        return count

我的做法(only前缀和 超出时间限制603/619)

from typing import *

class Solution:
    def countInterestingSubarrays(self, nums: List[int], modulo: int, k: int) -> int:
        # 通过前缀和知晓每个子数组中nums[i] % modulo == k的个数也即cnt的个数
        n = len(nums)
        pre_cnt = [0 for _ in range(n+1)]

        for i in range(1,n+1):
            pre_cnt[i] = pre_cnt[i-1] + int(nums[i-1] % modulo == k)
        
        # 来一个逻辑掩码数组
        ans = 0
        for i in range(n):
            for j in range(i+1, n+1):
                ans += int((pre_cnt[j] - pre_cnt[i]) % modulo == k)

        return ans

nextpage

2962. 统计最大元素出现至少 K 次的子数组

中等

给你一个整数数组 nums 和一个 正整数 k

请你统计有多少满足 「 nums 中的 最大 元素」至少出现 k 次的子数组,并返回满足这一条件的子数组的数目。

子数组是数组中的一个连续元素序列。

示例 1:

输入:nums = [1,3,2,3,3], k = 2
输出:6
解释:包含元素 3 至少 2 次的子数组为:[1,3,2,3]、[1,3,2,3,3]、[3,2,3]、[3,2,3,3]、[2,3,3] 和 [3,3] 。

示例 2:

输入:nums = [1,4,2,1], k = 3
输出:0
解释:没有子数组包含元素 4 至少 3 次。

提示:

  • 1 <= nums.length <= 105
  • 1 <= nums[i] <= 106
  • 1 <= k <= 105

滑动窗口

class Solution:
    def countSubarrays(self, nums: List[int], k: int) -> int:
        max_elem = max(nums)
        counter = 0

        n = len(nums)
        ans = 0
        left = 0 
            
        for right in range(n):
            if nums[right] == max_elem:
                counter += 1
            while counter >= k:
                ans += n - right
                if nums[left] == max_elem:
                    counter -=1
                left += 1

        return ans

nextpage

1584. 连接所有点的最小费用

中等

给你一个points 数组,表示 2D 平面上的一些点,其中 points[i] = [xi, yi]

连接点 [xi, yi] 和点 [xj, yj] 的费用为它们之间的 曼哈顿距离|xi - xj| + |yi - yj| ,其中 |val| 表示 val 的绝对值。

请你返回将所有点连接的最小总费用。只有任意两点之间 有且仅有 一条简单路径时,才认为所有点都已连接。

示例 1:

img

输入:points = [[0,0],[2,2],[3,10],[5,2],[7,0]]
输出:20
解释:

我们可以按照上图所示连接所有点得到最小总费用,总费用为 20 。
注意到任意两个点之间只有唯一一条路径互相到达。

示例 2:

输入:points = [[3,12],[-2,5],[-4,1]]
输出:18

示例 3:

输入:points = [[0,0],[1,1],[1,0],[-1,1]]
输出:4

示例 4:

输入:points = [[-1000000,-1000000],[1000000,1000000]]
输出:4000000

示例 5:

输入:points = [[0,0]]
输出:0

提示:

  • 1 <= points.length <= 1000
  • -106 <= xi, yi <= 106
  • 所有点 (xi, yi) 两两不同。

prim算法

import heapq
from pprint import pprint

class NDWGraph_dict:
    def __init__(self, N):
        self.graph = [{} for _ in range(N)]  # 邻接表格式: [{v2: weight, v3: weight, ...}, ...]

    def addEdge(self, v1, v2, weight):
        """添加无向边 (v1, v2) 和 (v2, v1),并赋予权重"""
        self.graph[v1][v2] = weight
        self.graph[v2][v1] = weight

    def removeEdge(self, v1, v2):
        """删除无向边 (v1, v2) 和 (v2, v1)"""
        if v2 in self.graph[v1]:
            del self.graph[v1][v2]
        if v1 in self.graph[v2]:
            del self.graph[v2][v1]

    def Show(self):
        """打印图的邻接字典形式"""
        print("当前无向带权图的邻接字典:")
        pprint(self.graph)

class Solution:
    def minCostConnectPoints(self, points: List[List[int]]) -> int:
        n = len(points)
        if n == 1:
            return 0

        G = NDWGraph_dict(n)

        # 计算曼哈顿距离并构建图
        for i in range(n):
            for j in range(i + 1, n):
                distance = abs(points[i][0] - points[j][0]) + abs(points[i][1] - points[j][1])
                G.addEdge(i, j, distance)

        def prim(graph):
            visited = set()
            heap = []
            total_cost = 0

            # 从节点 0 开始
            visited.add(0)
            for neighbor, weight in graph[0].items():
                heapq.heappush(heap, (weight, 0, neighbor))

            while heap and len(visited) < n:
                weight, u, v = heapq.heappop(heap)
                if v not in visited:
                    visited.add(v)
                    total_cost += weight
                    for neighbor, w in graph[v].items():
                        if neighbor not in visited:
                            heapq.heappush(heap, (w, v, neighbor))
            return total_cost

        return prim(G.graph)

nextpage

图论的几种算法(最小生成树/dijkstra算法/拓扑排序)

最小生成树算法(优化版)


最小生成树算法(自己写的)

def prim(G):
    n = len(G)
    v = 0
    s = {v}

    edges = []
    res = []

    for _ in range(n - 1):
        for u,w in G[v].items():
            heapq.heappush(edges, (w, v, u))

        while edges:
            weitght,cur,next = heapq.heappop(edges)
            if next not in s:
                s.add(next)
                res.append(((cur,next), weitght))
                v = next
                break
        else:
            raise Exception("not connected graph")

    return res

dijkstra算法/最小路径算法(背就完事了)

import heapq

def dijkstra(G, s):
    """
    优化后的Dijkstra算法实现
    
    参数:
        G: 图的邻接表表示,格式为 {v: {u: weight, ...}, ...}
        s: 起始节点
    
    返回:
        D: 从s到各节点的最短距离字典
        P: 前驱节点字典,用于重建路径
    """
    inf = float('inf')
    n = len(G)
    D = {v: inf for v in G}  # 最短距离字典
    D[s] = 0
    P = {v: None for v in G}  # 前驱节点字典
    
    # 优先队列,存储(距离, 节点)元组
    heap = []
    heapq.heappush(heap, (0, s))
    
    visited = set()  # 已确定最短路径的节点
    
    while heap:
        current_dist, v = heapq.heappop(heap)
        
        # 如果已经处理过该节点,跳过
        if v in visited:
            continue
            
        visited.add(v)
        
        # 遍历所有邻接节点
        for u, w in G[v].items():
            if u in visited:
                continue
                
            new_dist = current_dist + w
            if new_dist < D[u]:
                D[u] = new_dist
                P[u] = v
                heapq.heappush(heap, (new_dist, u))
    
    return D, P

拓扑排序(优化版)

from collections import deque

def topsort(G):
    # 计算每个节点的入度
    indegrees = {v: 0 for v in G}
    for v in G:
        for u, _ in G[v]:
            indegrees[u] += 1
    
    # 初始化队列,将所有入度为0的节点加入队列
    q = deque([v for v in G if indegrees[v] == 0])
    visited = set(q)  # 记录已访问节点
    result = []       # 存储拓扑排序结果
    
    while q:
        cur = q.popleft()
        result.append(cur)
        
        # 处理当前节点的所有邻接节点
        for neighbor, _ in G[cur]:
            indegrees[neighbor] -= 1
            # 如果邻接节点的入度减为0且未被访问过
            if indegrees[neighbor] == 0 and neighbor not in visited:
                visited.add(neighbor)
                q.append(neighbor)
    
    # 检查是否有环
    if len(result) != len(G):
        print("图中存在环,无法完成拓扑排序")
        return None
    
    return result

拓扑排序(自己写的)

def topsort(G):
    def bfs(G):
        S = set()
        q = deque()

        for v in G:
            if indegrees[v] == 0:
                if v not in S:
                    S.add(v)
                    q.append(v)

        while q:
            print((cur := q.popleft(),"实际上:",cur+1))

            for sons,_ in G[cur]:
                indegrees[sons] -= 1

            # 这里进行了重复遍历,效率很低
            for v in G:
                if indegrees[v] == 0:
                    if v not in S:
                        S.add(v)
                        q.append(v)


    indegrees = {v : 0 for v in G}
    for v in G:
        for u,_ in G[v]:
            indegrees[u] += 1
    bfs(G)

nextpage

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值