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。