1/20, BFS, Search
BFS is good at finding the Shortest path and topological sort
BFS用Queue来实现对有向或无向图的访问,
General template using queue
1)add initial node to the queue.
2)pop a node, and for each neighbor of it, check whether the neighbor/out node can be visited later.
3)if true, append to the queue, continue.
Word Ladder
- 无向图
因为要找的是最短路线,所以用BFS而不是DFS。
时间复杂度O(L^2 * 25), L is the length of each word and the size of the dict。
class Solution {
public int ladderLength(String beginWord, String endWord, List<String> wordList) {
Set<String> set = new HashSet<>(); // mark the unvisited string
for (String s : wordList) {
set.add(s);
}
Deque<String> queue = new LinkedList<>();
queue.offer(beginWord);
set.remove(beginWord);
int len = 0;
while (!queue.isEmpty()) {
int size = queue.size();
len++;
for (int k = 0; k < size; ++k) {
String s = queue.poll();
char[] ch = s.toCharArray();
for (int i = 0; i < s.length(); ++i) {
char cur = s.charAt(i);
for (char c = 'a'; c <= 'z'; ++c) {
ch[i] = c;
String newStr = String.valueOf(ch);
if (newStr.equals(s)) continue; // skip itself
if (newStr.equals(endWord) && set.contains(newStr)) return len + 1; // found the target word, return len;
if (!set.contains(newStr)) continue; // cannot find the next word
set.remove(newStr);
queue.offer(newStr);
}
ch[i] = cur;
}
}
}
return 0;
}
}
在queue里存入tuple,每个tuple包含当前的word和到这个word所用的step。每次循环中,queue只pop一个word,但可能被push进去若干个next words。使用queue的好处是,每次pop出的word都会是step最少的。
找到一个单词的下一个单词,加入queue后,要从dict里删除,因为不删除的话,会造成死循环。删除对最短路径没有影响,因为就算之后有其他单词能转化得到它,也不会比当前得到的路径短。
1/5刷又没remove,一定要做到bug free啊。
Shortest Path (HeartFlow OA Question)
Given a directed graph G, two nodes S and E, write a program that finds the shortest path between S and E(starting on S and ending on E). Assume there is a unique shortest path for each case.
- 有向图
#edges = {"A":["B"], "B":[]}
#start = "A"
#end = "B"
#output = ["A", "B"]
#edges = {"A":["B", "C"], "B":["D"], "C":["E"], "D":["E"], "E":["F"]}
#start = "A"
#end = "F"
#output = ['A', 'C', 'E', 'F']
#edges = {"A":["B"], "M":[]}
#start = "A"
#end = "M"
def shortestPath(start, end, edges):
from collections import deque
q = deque([(start, [])])
while q:
top, path = q.popleft()
if top == end:
path += [top]
return path
try:
for node in edges[top]:
q.append((node, path+[top]))
except KeyError:
return None
Perfect Square
The least number of perfect square numbers which sum to n can be regarded as the Shortest-path problem.
class Solution {
public int numSquares(int n) {
Deque<Integer> queue = new LinkedList<>();
queue.offer(0);
int count = 0;
Set<Integer> visited = new HashSet<>();
visited.add(0);
while (!queue.isEmpty()) {
int size = queue.size();
for (int sz = 0; sz < size; ++sz) {
int cur = queue.poll();
if (cur > n) {
continue;
}
if (cur == n) {
return count;
}
for (int i = 1; cur + i * i <= n; ++i) {
if (visited.contains(cur + i * i)) {
continue;
}
queue.offer(cur + i * i);
visited.add(cur + i * i);
}
}
count++;
}
return -1;
}
}
or we can solve the problem using DP
state definition
- perfect square numbers sum
state function
- state[i], min number of perfect square numbers sum to i
goal state
- state[n]
base case
- state[0] = 0
state transition
- min(state[i - j * j] + 1) = state[i]
filling direction
- i, j increasing
class Solution {
public int numSquares(int n) {
int[] state = new int[n + 1];
for (int i = 1; i <= n; ++i) {
state[i] = i; // initalize to all 1...
for (int j = 1; j <= i; j++) {
if (j * j > i) {
break;
}
state[i] = Math.min(state[i - j*j] + 1, state[i]);
}
}
return state[n];
}
}
12/07复习补充
Clone Graph
- 无向图的复制
BFS,搜索当前node的所有neighbors。
用个hashtable存访问过的node,{node:copied_node}。
class Solution:
# @param node, a undirected graph node
# @return a undirected graph node
def __init__(self):
self.dict = {}
def cloneGraph(self, node):
if not node:
return None
from collections import deque
queue = deque([node])
copied_node = UndirectedGraphNode(node.label)
self.dict[node] = copied_node
while queue:
cur = queue.popleft()
copied_cur = self.dict[cur]
for n in cur.neighbors:
if not n in self.dict:
copied_n = UndirectedGraphNode(n.label)
copied_cur.neighbors.append(copied_n)
self.dict[n] = copied_n
queue.append(n)
else:
copied_n = self.dict[n]
copied_cur.neighbors.append(copied_n)
return copied_node
12/29复习注,是先把第一个node加入queue,每次queue出列一个node,然后检查这个node的所有neighbors,如果没访问过,就update 新节点的neighbors,还要把neighbor节点加入queue,并且update hashtable。
1/5复习注,用到hashtable和queue。hashtable两个作用,一是添加访问过的节点,这样的话就能在queue里添加未访问的节点。二是mapper出新节点。
Course Schedule
- 有向图
利用入度和出度,adjacency list。
思路是由入度为0的节点开始,通过adjacency list来查找它出去的neighbors,并且去减掉neighbors的入度。当入度减为0时,就表示这个节点(课程)可以抵达,并累加课程数。edge case要判断有无环,有环的话queue是empty,返回false。
class Solution:
def canFinish(self, numCourses, prerequisites):
edges = {i: [] for i in range(numCourses)} ## out adjacency list {precourse:[courses]}
degrees = [0 for i in range(numCourses)] ## a list of nodes' in degree
for i, j in prerequisites:
edges[j].append(i)
degrees[i] += 1
import collections
queue, count = collections.deque(), 0
for i in range(numCourses):
if degrees[i] == 0: ## add nodes with 0 in-degree to a queue
queue.append(i)
while queue:
node = queue.popleft()
count += 1
for x in edges[node]: ## scan the 0 in-degree node's out neighbors
degrees[x] -= 1 ## decrease the in-degree of neighbors by 1
if degrees[x] == 0:
queue.append(x)
return count == numCourses
2/14补充,
Course Schedule II (Order Dependency)
Amazon的九道OA之一,跟上题的思路很接近。不同之处是,order的排序是根据order name,而不是order本身。而最后的输出是一个list of orders,所以需要用到dict{ordername:order},根据排好的ordername来输出order。另外需要两个dict,一个记录入度{ordername:num_count},一个出度{ordername:[ordername list]}。
时间复杂度是O(n),因为每个order只被push/pop一次。
class Order:
def __init__(self, ordername):
self.orderName = ordername
class OrderDependency:
def __init__(self, cur, pre):
self.cur = cur
self.pre = pre
class Solution:
def dependency(self, orderDependencies):
res = []
inmap, outmap, recordmap = {}, {}, {}
for orderDp in orderDependencies:
cur = orderDp.cur
pre = orderDp.pre
curName = cur.orderName
preName = pre.orderName
if not curName in recordmap:
recordmap[curName] = orderDp.cur
if not preName in recordmap:
recordmap[preName] = orderDp.pre
if not preName in inmap:
inmap[preName] = 0
if curName in inmap:
inmap[curName] += 1
else:
inmap[curName] = 1
if not preName in outmap:
outmap[preName] = []
outmap[preName].append(curName)
if not curName in outmap:
outmap[curName] = [] ## pay attention, each order should have an out list
from collections import deque
queue = deque([])
for name in inmap:
if inmap[name] == 0:
queue.append(name)
while queue:
top = queue.popleft()
res.append(recordmap[top])
for out in outmap[top]:
inmap[out] -= 1
if inmap[out] == 0:
queue.append(out)
return res
## test case
o0 = Order('泡面')
o1 = Order('热水')
o2 = Order('Z')
o3 = Order('M')
o4 = Order('O')
o5 = Order('A')
o6 = Order('N')
o7 = Order('A')
od0 = OrderDependency(o7, o0)
od1 = OrderDependency(o3, o0)
od2 = OrderDependency(o5, o3)
od3 = OrderDependency(o2, o1)
od4 = OrderDependency(o6, o4)
od5 = OrderDependency(o4, o2)
dependencies_list = [od1, od3, od2, od4, od0, od5]
s = Solution()
res = s.dependency(dependencies_list)
for od in res:
print(od.orderName) ## 泡面->热水->M->Z->A->O->N
和上面的solution区别在inmap和outmap的初始化方式,这里不用考虑course存不存在。
class Solution:
def findOrder(self, numCourses, prerequisites):
inmap = {i: 0 for i in range(numCourses)} ## or use a list to save some place
outmap = {i: [] for i in range(numCourses)}
res = []
for cur, pre in prerequisites:
inmap[cur] += 1
outmap[pre].append(cur)
from collections import deque
queue = deque([])
for i in range(numCourses):
if inmap[i] == 0:
queue.append(i)
while queue:
top = queue.popleft()
res.append(top)
for n in outmap[top]:
inmap[n] -= 1
if inmap[n] == 0:
queue.append(n)
if numCourses == len(res): ## pay attention
return res
return []
Search Graph Nodes
- 无向图
用queue找到距离给的点最短的点,利用给的hashtable,找值并且删掉访问过的点,防止无限循环。
class Solution:
def searchNode(self, graph, values, node, target):
from collections import deque
q = deque([node])
if values[node] == target: ## pay attention, check the node itself first
return node
del values[node] ## remove the node visited
while q:
top = q.popleft()
for n in top.neighbors: ## check each neighbor of top has been visited or not
if n in values: ## if not visited yet
if values[n] == target:
return n
del values[n] ## remove
q.append(n)
return None
Topological Sorting
- 有向图
输入为一个list of nodes,先要通过它得到入度。
class Solution:
def topSort(self, graph):
inmap = {} ## 用dict保存 {节点:入度}
for node in graph:
inmap[node] = 0
for i in graph:
for j in i.neighbors:
inmap[j] += 1
from collections import deque
q = deque([])
for node in graph:
if inmap[node] == 0:
q.append(node)
res = []
while q:
top = q.popleft()
res.append(top)
for n in top.neighbors: ## 已知每个节点的neighbors
inmap[n] -= 1
if inmap[n] == 0:
q.append(n)
return res
这道题的另一种解法是用DFS,区别是迭代和用queue储存可以访问的节点,相比之下BFS的方法能够减少function calls。
Word Ladder II
上题的follow up,要找所有最短的路径,BFS+DFS。