Skip to content

Commit a7ff0c5

Browse files
author
aaron.liu
committed
update
1 parent e36d810 commit a7ff0c5

7 files changed

+281
-1
lines changed

BFS/LC210 Course Schedule II.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
'''
2+
https://leetcode.com/problems/course-schedule-ii/description/
3+
返回任意一个拓扑序列 可以用bfs解决
4+
注意: 有向无环图才有拓扑序 有环图一定没有拓扑序
5+
算法框架:
6+
a. 将图中所有入度为零的点入队列
7+
queue <-- all nodes with in-degree 0
8+
b.
9+
while queue is not not empty:
10+
t <- q.front()
11+
枚举t的所有出边 t <-- j
12+
删掉出边t->j 并将j的入度减一: in_degree[j] -= 1
13+
如果in_degree[j] == 0: 将j点入队列 queue <- j
14+
最后队列中的元素顺序 就是一个拓扑序
15+
'''
16+
import collections
17+
from typing import List
18+
19+
class Solution:
20+
def findOrder(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]:
21+
graph = collections.defaultdict(list)

BFS/LC317 Shortest Distance from All Buildings.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,33 @@
44
'''
55

66
from typing import List
7+
from collections import deque
78
from collections import defaultdict
89

910
class Solution:
1011
def shortestDistance(self, grid: List[List[int]]) -> int:
1112
m, n = len(grid), len(grid[0])
12-
dist, k = defaultdict(int), 0
13+
dist, cnt = defaultdict(int), 0
14+
for i in range(m):
15+
for j in range(n):
16+
if grid[i][j] == 1:
17+
self.bfs(grid, m, n, i, j, cnt, dist)
18+
cnt += 1
19+
min_dist = float("inf")
20+
for (x, y), step in dist.items():
21+
if grid[x][y] == -cnt:
22+
min_dist = min(min_dist, step)
23+
return min_dist if min_dist != float("inf") else -1
24+
25+
def bfs(self, grid, m, n, i, j, cnt, dist):
26+
directions = [(-1, 0), (1, 0), (0, -1), (0, 1)]
27+
queue = deque([(0, i, j)])
28+
while queue:
29+
step, x, y = queue.popleft()
30+
step += 1
31+
for dx, dy in directions:
32+
nx, ny = x + dx, y + dy
33+
if 0 <= nx < m and 0 <= ny < n and grid[nx][ny] == -cnt:
34+
queue.append((step, nx, ny))
35+
grid[nx][ny] -= 1
36+
dist[(nx, ny)] += step
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
'''
2+
https://leetcode.com/problems/random-pick-with-weight/
3+
4+
w: [1, 2, 3]
5+
idx: 0 1 2
6+
随机返回一个下标 要求返回下标对应的权重 与总权重(每个权重相加)成正比
7+
先把w的值想象成一个个线段:
8+
A B C
9+
<-><--><--->
10+
1 3 6
11+
A,B,C三个区间长度分别为1,2,3 (1,3,6)是区间总长度
12+
假如现在有个[1,6]之间随机数 落在哪个区间上 就返回对应区间的下标即可
13+
比如落在B区间 返回B区间对应的下标1 就符合下标对应的权重与总权重成正比
14+
如何实现上述操作? 转化成整数二分+前缀和
15+
观察到:(1,3,6)这几个区间长度的尾端点 就是w的前缀和
16+
prefix sum: 0 1 2 3 4 5 6 (注意只有1,3,6是前缀和 其它数字写出来方便后续理解)
17+
区间: <-><--><---->
18+
区间编号: A B C
19+
w idx: 0 1 2
20+
21+
问题转化为: 随机出一个[1,max_prefix_sum]之间的整数target 然后二分前缀和数组
22+
找到第一个大于等于target的值 等价于找到了这个随机数落在了哪个长度的区间内
23+
二分找到的值 代表了所在区间长度的右端点 它的下标 也就是w原数组的下标
24+
'''
25+
26+
import random
27+
from typing import List
28+
29+
class Solution:
30+
def __init__(self, w: List[int]):
31+
self.prefix_sum = [0] * len(w)
32+
self.prefix_sum[0] = w[0]
33+
for i in range(1, len(w)):
34+
self.prefix_sum[i] = self.prefix_sum[i - 1] + w[i]
35+
def pickIndex(self) -> int:
36+
target = random.randint(1, self.prefix_sum[-1]) # 随机一个[1, max_prefix_sum]之间的整数
37+
# 二分找第一个大于等于target的前缀和的值 并返回对应的下标
38+
left, right = 0, len(self.prefix_sum) - 1
39+
while left < right:
40+
mid = (left + right) // 2
41+
if self.prefix_sum[mid] < target: # mid对应的值比target小 一定不是答案 所以mid+1跳过mid
42+
left = mid + 1
43+
else:
44+
right = mid # mid对应的值>=target 可能是答案 所以不能跳过mid
45+
return left
46+
47+
# Your Solution object will be instantiated and called as such:
48+
# obj = Solution(w)
49+
# param_1 = obj.pickIndex()

DFS/Uber all topo sort.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
'''
2+
same set up as https://leetcode.com/problems/course-schedule-ii/
3+
it requries to return all topological sort
4+
1. 根据边与边的关系 建立有向图 注意边的指向
5+
2. 建图过程中 构建in_degree数组:in_degree[i]代表i号点的入度
6+
3. dfs找所有的path dfs过程如下
7+
a. 遍历所有的点 如果当前点x满足如下条件:
8+
- 入度为0: in_degree[x] == 0
9+
- 没有在当前的路径上遍历过 visited[x] == False
10+
将该点放入探索路径上 tmp.append(x)
11+
b. 记录当前点x为visited 并把它的出边的所有入度都减1(in_degree[i] -= 1 for all x->i)
12+
c. 递归调用dfs
13+
d. dfs调用返回 还原所有状态:
14+
- 还原x的出边点所有的入度(in_degree[i] += 1 for all x->i)
15+
- 当前探索路径上删掉x: tmp.pop()
16+
- 当前点x记录为un-visited
17+
18+
Time Complexity: O(V!) V is the number of vertices, V! is absolute worst case.
19+
(worst case example: any graph with no edges at all)
20+
Space: O(V) for creating an additional array and recursive stack space.
21+
'''
22+
from typing import List
23+
import collections
24+
25+
def findOrder(numCourses: int, prerequisites: List[List[int]]) -> List[int]:
26+
graph = collections.defaultdict(list)
27+
in_degree = [0] * numCourses
28+
res = []
29+
visited = [False] * numCourses
30+
for x, y in prerequisites:
31+
graph[y].append(x)
32+
in_degree[x] += 1
33+
find_all_topo_orders(res, graph, in_degree, [], numCourses, visited)
34+
return res
35+
36+
def find_all_topo_orders(res, graph, in_degree, tmp, n, visited):
37+
if len(tmp) == n:
38+
print("found one:", tmp)
39+
res.append(tmp[:])
40+
return
41+
42+
for vtx in range(n):
43+
if in_degree[vtx] == 0 and not visited[vtx]:
44+
visited[vtx] = True
45+
tmp.append(vtx)
46+
for nei in graph[vtx]:
47+
in_degree[nei] -= 1
48+
find_all_topo_orders(res, graph, in_degree, tmp, n, visited)
49+
for nei in graph[vtx]:
50+
in_degree[nei] += 1
51+
tmp.pop()
52+
visited[vtx] = False
53+
54+
n = 4
55+
p = [[1,0], [2,0], [3,2], [3,1]]
56+
n = 3
57+
# p = [[1,0], [2,1], [0,2]]
58+
print(findOrder(n, p))

DP/LC213 House Robber II.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
'''
2+
https://leetcode.com/problems/house-robber-ii/?envType=company&envId=apple&favoriteSlug=apple-three-months
3+
4+
状态机dp
5+
'''
6+
from typing import List
7+
8+
class Solution:
9+
# 状态机dp
10+
def rob(self, nums: List[int]) -> int:
11+
if len(nums) == 1:
12+
return nums[0]
13+
size = len(nums)
14+
# f[i][j] 第i号点 取(j==1)或者不取(j==0)
15+
dp = [[0,0] for i in range(size + 1)]
16+
# 枚举第1号点 取或者不取. 它决定了最后一个点是否能取
17+
# 第1号点取 dp[1][0]这个状态就非法 赋值负无穷
18+
dp[1][0] = -1e5
19+
dp[1][1] = nums[0]
20+
for i in range(2, size + 1):
21+
dp[i][1] = dp[i - 1][0] + nums[i - 1]
22+
dp[i][0] = max(dp[i - 1][1], dp[i - 1][0])
23+
24+
ret = dp[size][0] # 第1号点取了 最后一个点只能不取
25+
26+
# 第1号点不取 dp[1][1]状态非法 赋值负无穷
27+
dp[1][1] = -1e5
28+
dp[1][0] = 0 # 注意这里要对dp[1][0]重新赋值 上面的逻辑修改过dp[1][0]
29+
for i in range(2, size + 1):
30+
dp[i][1] = dp[i - 1][0] + nums[i - 1]
31+
dp[i][0] = max(dp[i - 1][1], dp[i - 1][0])
32+
33+
ret = max(ret, dp[size][1], dp[size][0])
34+
return ret
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
'''
2+
(队列实现滑动窗口) O(n)
3+
首先我们可以知道,对于每个位置而言,只有初始状态和总共被反转了多少次决定了自己最终的状态。另一方面,我们知道每一个长度为K的区间,最多只会被反转一次,因为两次反转后对最终结果没有影响。基于此,我们从前往后遍历数组,如果遇到一个0,我们将当前位置开始的长度为k区间的区间反转。如果遇到0时,剩下的区间长度不足K说明我们没有办法完成反转。但是如果我们每次反转当前区间时,将区间内每个数都取反,时间复杂度是O(n∗k)的,这样是不够快的。需要优化一下,我们再考虑每个位置上的元素,他只会被前面K - 1个元素是否被反转所影响,所以我们只需要知道前面k - 1个元素总共反转了多少次(更进一步的说我们只关系反转次数的奇偶性)。
4+
5+
我们使用一个队列保存i前面k - 1个位置有多少元素被反转了。
6+
7+
如果队列长度为奇数,那么当前位置的1被变成0了需要反转,如果为偶数,说明当前位置的0还是0,需要反转。
8+
9+
如果最后k - 1个位置还有0的话说明失败。否则将i加入队列,更新答案。
10+
11+
时间复杂度:每个元素最多被进入队列和出队列一次,所以总的时间复杂度为O(n)
12+
'''
13+
from typing import List
14+
from collections import deque
15+
16+
class Solution:
17+
def minKBitFlips(self, nums: List[int], k: int) -> int:
18+
n, ret = len(nums), 0
19+
queue = deque([]) # 存储当前位置的 前k - 1个元素中 需要翻转的元素下标 也代表了能影响到当前位置的翻转次数
20+
for i in range(n): # 从前往后遍历每个元素
21+
while queue and queue[0] + k <= i: # 队列元素中的个数大于等于k 需要pop最早入队的 它影响不到当前i位置
22+
queue.popleft()
23+
24+
if nums[i] == len(queue) % 2: # 两种情况当前位置需要翻转 1.队列长度是奇数且当前值是1(奇数长度的队列会把当前值转成0) 2.队列长度是偶数且当前值是0(之后需要转成1)
25+
if i + k > n: # 最后元素不足k个了 无论如何也无法满足条件: 一次翻转必须转exact k个 无解
26+
return -1
27+
queue.append(i) # 当前下标入队
28+
ret += 1 #更新所求答案
29+
return ret

Tree/uber n ary tree arch view.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
'''
2+
Given an N-ary tree, print the nodes seen by a person when walking from
3+
bottom left to bottom right in an arc via root node.
4+
题目描述故意给的很模糊 只能靠猜+跟对方反复确认理解.
5+
例子1
6+
1
7+
/ | \
8+
2 3 4
9+
/ \ / \
10+
5 6 7 8
11+
输出: [5,2,1,4,8]
12+
13+
例子2:
14+
1
15+
/ | \
16+
2 3 4
17+
\ /
18+
6 7
19+
输出:[6,2,1,4,7]
20+
21+
'''
22+
from collections import deque, defaultdict
23+
from typing import List
24+
25+
class Node:
26+
def __init__(self, val=0):
27+
self.val = val
28+
self.children = []
29+
30+
def tree_arch_view(root: Node) -> List[int]:
31+
ret = []
32+
if not root:
33+
return ret
34+
35+
map = defaultdict(lambda: [None] * 2) # key是level, value是[0:left_most, 1:right_most]
36+
queue = deque([root])
37+
level = 0
38+
39+
while queue:
40+
size = len(queue)
41+
for i in range(size):
42+
node = queue.popleft()
43+
if i == 0: # 当前层的第一个点 是left most node
44+
map[level][0] = node.val
45+
if size > 1 and i == size - 1: # 当前层的节点数>1(就一个点的话 肯定是left most) 且当前点是当前层最后一个点: right most node
46+
map[level][1] = node.val
47+
for child in node.children: # 将当前点的所有子节点 从左到右入队
48+
queue.append(child)
49+
level += 1 # 注意bfs结束时 level比实际值多加了1
50+
# print("map:", map)
51+
# 从左下角开始往上遍历 所以层数倒着数
52+
for i in range(level - 1, -1, -1): # bfs结束时 level比实际值多加了1 所以从level-1开始倒着遍历
53+
ret.append(map[i][0])
54+
# 从第二层开始沿着最右侧往下遍历 第一层的根节点 已在倒着遍历时考虑到了
55+
for i in range(1, level):
56+
if map[i][1]: # 某一层可能没有right most node 需要判断一下
57+
ret.append(map[i][1])
58+
return ret
59+
60+
root = Node(1)
61+
root.children = [Node(2), Node(3), Node(4)]
62+
root.children[0].children = [Node(5), Node(6)]
63+
root.children[2].children = [Node(7)]
64+
65+
print(tree_arch_view(root))

0 commit comments

Comments
 (0)