59. 螺旋矩阵 II
中等
给你一个正整数 n
,生成一个包含 1
到 n2
所有元素,且元素按顺时针顺序螺旋排列的 n x n
正方形矩阵 matrix
。
示例 1:
输入: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:
输入:head = [1,1,2]
输出:[1,2]
示例 2:
输入: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:
输入:head = [1,2,3,3,4,4,5]
输出:[1,2,5]
示例 2:
输入: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
中等
给你四个整数数组 nums1
、nums2
、nums3
和 nums4
,数组长度都是 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 == c
且 b == d
) 或者 (a == d
且 b == 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 开始的整数数组tasks
和 workers
以及两个整数 pills
和 strength
,请你返回 最多 有多少个任务可以被完成。
示例 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:
输入: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)