Skip to content

Commit e28c38a

Browse files
author
Chris Wu
committed
multiple updates
1 parent d61d2aa commit e28c38a

File tree

5 files changed

+209
-10
lines changed

5 files changed

+209
-10
lines changed

course-schedule.py

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
"""
2-
we build a graph of adjacency list, like [0]
2+
First, we build a graph of adjacency list #[0]
33
0->[2,4,5]
44
1->[3,4]
5-
meaning before taking 2,4,5 we need to take 0, before taking 3,4 we need to take 1
5+
Meaning before taking 2,4,5 we need to take 0, before taking 3,4 we need to take 1
66
if we find a loop back to itself then it is impossible, for example
77
0->[2,4,5]
88
1->[3,4]
99
2->[0,3]
10-
0->2->0, which is imposible
10+
0->2->0, which is imposible.
1111
12-
now we iterate every course to see if it can loop back to itself in anyway [1]
12+
Now we iterate every course to see if it can loop back to itself in anyway #[1]
1313
we do this by dfs and search for it self
1414
if we find itself we find loop
1515
16-
the time efficiency is O(V^2+VE), bc each dfs in adjacency list is O(V+E) and we do it V times
17-
space efficiency is O(E)
18-
V is the numCourses(Vertices)
19-
E is the prerequisites(Edges)
16+
The time efficiency is O(V^2+VE), because each dfs in adjacency list is O(V+E) and we do it V times
17+
Space efficiency is O(E).
18+
V is the numCourses (Vertices).
19+
E is the number of prerequisites (Edges).
2020
"""
2121
class Solution(object):
2222
def canFinish(self, numCourses, prerequisites):
@@ -35,4 +35,48 @@ def canFinish(self, numCourses, prerequisites):
3535
for ajc in graph[course]:
3636
if ajc not in visited:
3737
stack.append(ajc)
38-
return True
38+
return True
39+
40+
"""
41+
Topological sort works only in directed graph.
42+
We can use it to know which node comes after which or detect cycles.
43+
The algorithm is easy to understand.
44+
First, we build the adjacent list and count all the inbound of the node.
45+
Then we start from the node whose inbound count is 0, adding it in to the `q_next`.
46+
For every node we pop out from q_next
47+
* We remove the node's outbound by decrease 1 on all its neighbor's inbound.
48+
* Put the node's neighbor to `q_next` if it has no inbound
49+
* Put the node into the `q`
50+
Repeat the process until there is no more node.
51+
The order in the `q` is the order we are going to encounter when we run through the directed graph.
52+
If we cannot sort all the nodes in the graph, it means that there are some nodes we couldn't find its starting point, in other words, there are cycles in the graph.
53+
54+
Time: O(E+2V) ~= O(E+V)
55+
we used O(E) to build the graph #[1], O(V) to find the starting point #[2], then traverse all the nodes again #[3].
56+
Space: O(E+3V) ~= O(E+V), O(E+V) for the adjacent list. O(V) for the `q`, O(V) for the `q_next`.
57+
"""
58+
class Solution(object):
59+
def canFinish(self, numCourses, prerequisites):
60+
graph = collections.defaultdict(list)
61+
inbound = collections.defaultdict(int)
62+
q_next = collections.deque()
63+
q = collections.deque()
64+
65+
for n1, n2 in prerequisites: #[1]
66+
graph[n1].append(n2)
67+
inbound[n2]+=1
68+
69+
for n in xrange(numCourses): #[2]
70+
if inbound[n]==0:
71+
q_next.append(n)
72+
73+
while q_next: #[3]
74+
n = q_next.popleft()
75+
for nei in graph[n]:
76+
inbound[nei]-=1
77+
if inbound[nei]==0:
78+
q_next.append(nei)
79+
q.append(n)
80+
return len(q)==numCourses
81+
82+

friend-circles.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
"""
2+
For every person #[1]
3+
We use `dfs()` to traverse all its network and mark all the visited people. #[2]
4+
So if the someone is visited, this means that he/she is in other people's friend cicle already. #[3]
5+
So If Bob is never visited, `circles` adds 1 to represent all the people in the new network. #[4]
6+
7+
Time Complexity is O(N^2), we at most travel every node in the 2D array.
8+
Space Complexity is O(N).
9+
"""
10+
class Solution(object):
11+
def findCircleNum(self, M):
12+
def dfs(i):
13+
stack = [i]
14+
while stack and len(stack)>0:
15+
node = stack.pop()
16+
visited.add(node) #[2]
17+
for j in xrange(len(M)):
18+
if M[node][j]==1 and j not in visited:
19+
stack.append(j)
20+
21+
circles = 0
22+
visited = set()
23+
for i in xrange(len(M)): #[1]
24+
if i in visited: continue #[3]
25+
dfs(i)
26+
circles+=1 #[4]
27+
28+
return circles
29+
"""
30+
We use the `roots` to store every node's root.
31+
So if at index 1 value is 5, means that node1's root is node5. #[1]
32+
33+
We use `find()` to find a node's root by finding the node's parent and parent's parnet... #[2]
34+
Along the way, we also update all the node we encounter.
35+
This technique is also called path compression.
36+
37+
So for every connection, we `union()`, making those two node and all of their parent point to the same root. #[3]
38+
All the children under the root is in the same friend circle.
39+
40+
At last, we count how many unique root in `roots`. #[4]
41+
42+
When we `find()`, we only update all the node's parents until we reach the root. #[5]
43+
We did not update the node's child.
44+
That is why we need to `find()` every node again.
45+
To make sure all nodes in the `roots` has the right value.
46+
47+
Time Complexity is O(N^2) for `find()` is really close to O(1) in average.
48+
Space Complexity is O(N)
49+
"""
50+
class Solution(object):
51+
def findCircleNum(self, M):
52+
def find(x):
53+
if x != roots[x]:
54+
roots[x] = find(roots[x])
55+
return roots[x]
56+
57+
def union(p1, p2): #[2]
58+
p1_root = find(p1)
59+
p2_root = find(p2)
60+
roots[p1_root] = p2_root
61+
62+
roots = [i for i in xrange(len(M))] #[1]
63+
64+
for p1 in xrange(len(M)):
65+
for p2 in xrange(len(M)):
66+
if M[p1][p2] == 1:
67+
union(p1, p2) #[3]
68+
69+
roots = [find(i) for i in roots] #[5]
70+
return len(set(roots)) #[4]
71+
72+
73+
M = [[1,0,0,1],[0,1,1,0],[0,1,1,1],[1,0,1,1]]
74+
s = Solution()
75+
s.findCircleNum(M)

redundant-connection.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
"""
2+
We use the `roots` to store the root of each node.
3+
For example, at index 1 value is 5, means that node1's root is node5.
4+
5+
We use `find()` to find the node's root. In other words, the node's parent's parent's ...
6+
Every node we encounter along the way, we update their root value in `roots` to the up most root. (This technique is called path compression).
7+
8+
So, for every edge, we unify `u` and `v` them. #[1]
9+
Which means u and v and all of their parents all lead to one root.
10+
11+
If u's root (`ur`) and v's root (`vr`) are already the same before we unify them.
12+
This edge is redundant.
13+
14+
This algorithm is called Union Find, often used in undirected graph cycle detect or grouping.
15+
If you wanted to detect cycles in directed graph, you need to use Topological sort.
16+
"""
17+
class Solution(object):
18+
def findRedundantConnection(self, edges):
19+
def find(x):
20+
if x != roots[x]:
21+
roots[x] = find(roots[x])
22+
return roots[x]
23+
24+
opt = []
25+
roots = range(len(edges))
26+
27+
for u, v in edges:
28+
# union
29+
ur = find(u)
30+
vr = find(v)
31+
32+
if ur == vr: #[2]
33+
opt = [u, v]
34+
else:
35+
roots[vr] = ur #[1]
36+
37+
return opt

remove-duplicates-from-sorted-list.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"""
2+
The key is to use a set to remember if we seen the node or not.
3+
Next, think about how we are going to *remove* the duplicate node?
4+
The answer is to simply link the previous node to the next node.
5+
So we need to keep a pointer `prev` on the previous node as we iterate the linked list.
6+
7+
So, the solution.
8+
Create a set `seen`. #[1]
9+
Point pointer `prev` on the first node. `cuur` on the second.
10+
Now we iterate trough the linked list.
11+
* For every node, we add its value to `seen`. Move `prev` and `curr` forward. #[2]
12+
* If we seen the node, we *remove* the `curr` node. Then move the curr forward. #[3]
13+
Return the `head`
14+
"""
15+
class Solution(object):
16+
def deleteDuplicates(self, head):
17+
if head is None or head.next is None: return head
18+
19+
prev = head
20+
curr = head.next
21+
seen = set() #[1]
22+
23+
seen.add(prev.val)
24+
while curr:
25+
if curr.val not in seen: #[2]
26+
seen.add(curr.val)
27+
curr = curr.next
28+
prev = prev.next
29+
else: #[3]
30+
prev.next = curr.next #remove
31+
curr = curr.next
32+
return head

reverse-linked-list.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#https://leetcode.com/problems/reverse-linked-list/
22
class Solution(object):
3+
#iterative
34
def reverseList(self, head):
45
prev = None
56
current = head
@@ -8,6 +9,16 @@ def reverseList(self, head):
89
current.next = prev
910
prev = current
1011
current = temp
11-
12+
1213
return prev
1314

15+
#recursive
16+
def reverseList(self, head):
17+
if head is None or head.next is None:
18+
return head
19+
20+
new_head = self.reverseList(head.next)
21+
n = head.next
22+
n.next = head
23+
head.next = None
24+
return new_head

0 commit comments

Comments
 (0)