diff --git a/Hard/685 Redundant Connection II.md b/Hard/685 Redundant Connection II.md new file mode 100644 index 0000000..26f375c --- /dev/null +++ b/Hard/685 Redundant Connection II.md @@ -0,0 +1,76 @@ +# 685. Redundant Connection II + +## Intuition + +The problem requires us to find a redundant edge in a directed graph that causes either a cycle or a node with two parents. The key insight is that there are two possible cases: + +1. A node has two parents (in-degree > 1) +2. The graph contains a cycle + +## Approach + +1. First, we identify if there's a node with two parents by tracking the parent of each node. If found, we store the two candidate edges. +2. We use Union-Find (Disjoint Set Union) to detect cycles in the graph. +3. We process all edges except the second candidate edge (if it exists). +4. If we find a cycle: + - If we had a node with two parents, return the first candidate edge + - Otherwise, return the current edge that forms the cycle +5. If no cycle is found, return the second candidate edge (which must be the redundant one) + +## Complexity + +- Time complexity: O(n) +- Space complexity: O(n) + +## Keywords + +- Union-Find +- Graph +- Cycle Detection + +## Code + +```go +func findRedundantDirectedConnection(edges [][]int) []int { + parent, candidate1, candidate2 := make(map[int]int), -1, -1 + for i, edge := range edges { + if ii, found := parent[edge[1]]; found { + candidate1, candidate2 = ii, i + break + } + parent[edge[1]] = i + } + + root := make([]int, len(edges) + 1) + for i := range root { + root[i] = i + } + + findRoot := func(n int) int { + for root[n] != n { + root[n] = root[root[n]] + n = root[n] + } + return n + } + + for _, edge := range edges { + u, v := edge[0], edge[1] + if candidate2 != -1 && u == edges[candidate2][0] && v == edges[candidate2][1] { + continue + } + + ur, uv := findRoot(u), findRoot(v) + + if ur == uv { + if candidate1 != -1 { + return edges[candidate1] + } + return edge + } + root[uv] = ur + } + + return edges[candidate2] +} +``` diff --git a/Hard/847 Shortest Path Visiting All Nodes.md b/Hard/847 Shortest Path Visiting All Nodes.md new file mode 100644 index 0000000..7312e2f --- /dev/null +++ b/Hard/847 Shortest Path Visiting All Nodes.md @@ -0,0 +1,81 @@ +# 847. Shortest Path Visiting All Nodes + +## Intuition + +The problem requires finding the shortest path that visits all nodes in an undirected graph. We can think of this as a state-based BFS problem where each state consists of: + +1. The current node we're at +2. A bitmask representing which nodes we've visited so far + +## Approach + +1. Use BFS to explore all possible paths while keeping track of visited states +2. Each state is represented by a combination of: + - Current node position + - Bitmask indicating visited nodes (1 << i for node i) +3. Initialize the queue with all possible starting nodes +4. For each state, explore all neighboring nodes +5. Use a visited map to avoid revisiting the same state +6. The target is reached when all nodes are visited (bitmask equals 2^n - 1) + +## Complexity + +- Time complexity: O(n * 2^n) +- Space complexity: O(n * 2^n) + +## Keywords + +- BFS +- Bitmask +- State-based Search +- Graph Traversal +- Shortest Path + +## Code + +```go +func shortestPathLength(graph [][]int) int { + if len(graph) == 1 { + return 0 + } + + type unit struct { + n int + mask int + } + newUnit := func(i, m int) *unit { + return &unit{ + n: i, + mask: m, + } + } + + visited := make([]map[int]bool, len(graph)) + queue := make([]*unit, 0) + for i := range graph { + mask := 1 << i + visited[i] = make(map[int]bool) + queue = append(queue, newUnit(i, mask)) + } + + target, ret := 1 << len(graph) - 1, 1 + for { + n := len(queue) + for _, cur := range queue { + for _, nb := range graph[cur.n] { + nextMask := cur.mask | (1 << nb) + if !visited[nb][nextMask] { + if nextMask == target { + goto RETURN + } + visited[nb][nextMask], queue = true, append(queue, newUnit(nb, nextMask)) + } + } + } + queue, ret = queue[n:], ret + 1 + } + +RETURN: + return ret +} +``` diff --git a/Medium/210 Course Schedule II.md b/Medium/210 Course Schedule II.md new file mode 100644 index 0000000..df9ba72 --- /dev/null +++ b/Medium/210 Course Schedule II.md @@ -0,0 +1,79 @@ +# 210. Course Schedule II + +## Intuition + +The problem is essentially about finding a valid topological order of courses given their prerequisites. We need to determine if it's possible to complete all courses and return the order in which they should be taken. This is a classic topological sorting problem where we need to find a linear ordering of vertices such that for every directed edge (u, v), vertex u comes before v in the ordering. + +## Approach + +1. Create a graph representation using an adjacency list and track the in-degree (number of prerequisites) for each course +2. Initialize a queue with courses that have no prerequisites (in-degree = 0) +3. Perform a BFS-like traversal: + - Remove a course from the queue and add it to the result + - For each course that has this course as a prerequisite, decrease their in-degree + - If a course's in-degree becomes 0, add it to the queue +4. If the length of the result equals the total number of courses, return the result; otherwise, return an empty array indicating it's impossible to complete all courses + +## Complexity + +- Time complexity: O(V + E) +- Space complexity: O(V + E) + +## Keywords + +- Topological Sort +- Graph +- BFS + +## Code + +```go +func findOrder(numCourses int, prerequisites [][]int) []int { + type node struct { + ingress int + used bool + neighbor []int + } + + ret, record := make([]int, 0), make([]node, numCourses) + + for _, pre := range prerequisites { + a, b := pre[0], pre[1] + if !record[b].used { + record[b].neighbor = make([]int, 0) + } + record[a].used, record[b].used = true, true + record[a].ingress, record[b].neighbor = record[a].ingress + 1, append(record[b].neighbor, a) + } + + current := make([]int, 0) + for i, n := range record { + if !n.used { + ret = append(ret, i) + continue + } + if n.ingress == 0 { + current = append(current, i) + } + } + + for len(current) != 0 { + ret = append(ret, current...) + n := len(current) + for _, num := range current { + for _, nb := range record[num].neighbor { + record[nb].ingress -= 1 + if record[nb].ingress == 0 { + current = append(current, nb) + } + } + } + current = current[n:] + } + + if len(ret) != numCourses { + return []int{} + } + return ret +} +``` diff --git a/README.md b/README.md index 28296f7..583c7e4 100644 --- a/README.md +++ b/README.md @@ -23,15 +23,18 @@ | 135 | Candy | [go](/Hard/135%20Candy.md) | H | | 140 | Word Break II | [go](/Hard/140%20Word%20Break%20II.md) | H | | 146 | LRU Cache | [go](/Medium/146%20LRU%20Cache.md) | M | +| 210 | Course Schedule II | [go](/Medium/210%20Course%20Schedule%20II.md) | M | | 236 | Lowest Common Ancestor of a Binary Tree | [go](/Medium/236%20Lowest%20Common%20Ancestor%20of%20a%20Binary%20Tree.md) | M | | 239 | Sliding Window Maximum | [go](/Hard/239%20Sliding%20Window%20Maximum.md) | H | | 322 | Coin Change | [go](/Medium/322%20Coin%20Change.md) | M | | 435 | Non-overlapping Interval | [go](/Medium/435%20Non-overlapping%20Intervals.md) | M | | 460 | LFU Cache | [go](/Hard/460%20LFU%20Cache.md) | H | | 592 | Fraction Addition and Subtraction | [go](/Medium/592%20Fraction%20Addition%20and%20Subtraction.md) | M | +| 685 | Redundant Connection II | [go](/Hard/685%20Redundant%20Connection%20II.md) | H | | 752 | Open the Lock | [go](/Medium/752%20Open%20the%20Lock.md) | M | | 757 | Set Intersection Size At Least Two | [go](/Hard/757%20Set%20Intersection%20Size%20At%20Least%20Two.md) | H | | 815 | Bus Routes | [go](/Hard/815%20Bus%20Routes.md) | H | +| 847 | Shortest Path Visiting All Nodes | [go](/Hard/847%20Shortest%20Path%20Visiting%20All%20Nodes.md) | H | | 765 | Couples Holding Hands | [go](/Hard/765%20Couples%20Holding%20Hands.md) | H | | 834 | Sum of Distances in Tree | [go](/Hard/834%20Sum%20of%20Distances%20in%20Tree.md) | H | | 840 | Magic Squares In Grid | [go](/Medium/840%20Magic%20Squares%20In%20Grid.md) | M | diff --git a/Week10/210 AC.png b/Week10/210 AC.png new file mode 100644 index 0000000..0ce1e5e Binary files /dev/null and b/Week10/210 AC.png differ diff --git a/Week10/210 Course Schedule II.md b/Week10/210 Course Schedule II.md new file mode 100644 index 0000000..df9ba72 --- /dev/null +++ b/Week10/210 Course Schedule II.md @@ -0,0 +1,79 @@ +# 210. Course Schedule II + +## Intuition + +The problem is essentially about finding a valid topological order of courses given their prerequisites. We need to determine if it's possible to complete all courses and return the order in which they should be taken. This is a classic topological sorting problem where we need to find a linear ordering of vertices such that for every directed edge (u, v), vertex u comes before v in the ordering. + +## Approach + +1. Create a graph representation using an adjacency list and track the in-degree (number of prerequisites) for each course +2. Initialize a queue with courses that have no prerequisites (in-degree = 0) +3. Perform a BFS-like traversal: + - Remove a course from the queue and add it to the result + - For each course that has this course as a prerequisite, decrease their in-degree + - If a course's in-degree becomes 0, add it to the queue +4. If the length of the result equals the total number of courses, return the result; otherwise, return an empty array indicating it's impossible to complete all courses + +## Complexity + +- Time complexity: O(V + E) +- Space complexity: O(V + E) + +## Keywords + +- Topological Sort +- Graph +- BFS + +## Code + +```go +func findOrder(numCourses int, prerequisites [][]int) []int { + type node struct { + ingress int + used bool + neighbor []int + } + + ret, record := make([]int, 0), make([]node, numCourses) + + for _, pre := range prerequisites { + a, b := pre[0], pre[1] + if !record[b].used { + record[b].neighbor = make([]int, 0) + } + record[a].used, record[b].used = true, true + record[a].ingress, record[b].neighbor = record[a].ingress + 1, append(record[b].neighbor, a) + } + + current := make([]int, 0) + for i, n := range record { + if !n.used { + ret = append(ret, i) + continue + } + if n.ingress == 0 { + current = append(current, i) + } + } + + for len(current) != 0 { + ret = append(ret, current...) + n := len(current) + for _, num := range current { + for _, nb := range record[num].neighbor { + record[nb].ingress -= 1 + if record[nb].ingress == 0 { + current = append(current, nb) + } + } + } + current = current[n:] + } + + if len(ret) != numCourses { + return []int{} + } + return ret +} +``` diff --git a/Week10/685 AC.png b/Week10/685 AC.png new file mode 100644 index 0000000..d61c990 Binary files /dev/null and b/Week10/685 AC.png differ diff --git a/Week10/685 Redundant Connection II.md b/Week10/685 Redundant Connection II.md new file mode 100644 index 0000000..26f375c --- /dev/null +++ b/Week10/685 Redundant Connection II.md @@ -0,0 +1,76 @@ +# 685. Redundant Connection II + +## Intuition + +The problem requires us to find a redundant edge in a directed graph that causes either a cycle or a node with two parents. The key insight is that there are two possible cases: + +1. A node has two parents (in-degree > 1) +2. The graph contains a cycle + +## Approach + +1. First, we identify if there's a node with two parents by tracking the parent of each node. If found, we store the two candidate edges. +2. We use Union-Find (Disjoint Set Union) to detect cycles in the graph. +3. We process all edges except the second candidate edge (if it exists). +4. If we find a cycle: + - If we had a node with two parents, return the first candidate edge + - Otherwise, return the current edge that forms the cycle +5. If no cycle is found, return the second candidate edge (which must be the redundant one) + +## Complexity + +- Time complexity: O(n) +- Space complexity: O(n) + +## Keywords + +- Union-Find +- Graph +- Cycle Detection + +## Code + +```go +func findRedundantDirectedConnection(edges [][]int) []int { + parent, candidate1, candidate2 := make(map[int]int), -1, -1 + for i, edge := range edges { + if ii, found := parent[edge[1]]; found { + candidate1, candidate2 = ii, i + break + } + parent[edge[1]] = i + } + + root := make([]int, len(edges) + 1) + for i := range root { + root[i] = i + } + + findRoot := func(n int) int { + for root[n] != n { + root[n] = root[root[n]] + n = root[n] + } + return n + } + + for _, edge := range edges { + u, v := edge[0], edge[1] + if candidate2 != -1 && u == edges[candidate2][0] && v == edges[candidate2][1] { + continue + } + + ur, uv := findRoot(u), findRoot(v) + + if ur == uv { + if candidate1 != -1 { + return edges[candidate1] + } + return edge + } + root[uv] = ur + } + + return edges[candidate2] +} +``` diff --git a/Week10/847 AC.png b/Week10/847 AC.png new file mode 100644 index 0000000..97f8046 Binary files /dev/null and b/Week10/847 AC.png differ diff --git a/Week10/847 Shortest Path Visiting All Nodes.md b/Week10/847 Shortest Path Visiting All Nodes.md new file mode 100644 index 0000000..7312e2f --- /dev/null +++ b/Week10/847 Shortest Path Visiting All Nodes.md @@ -0,0 +1,81 @@ +# 847. Shortest Path Visiting All Nodes + +## Intuition + +The problem requires finding the shortest path that visits all nodes in an undirected graph. We can think of this as a state-based BFS problem where each state consists of: + +1. The current node we're at +2. A bitmask representing which nodes we've visited so far + +## Approach + +1. Use BFS to explore all possible paths while keeping track of visited states +2. Each state is represented by a combination of: + - Current node position + - Bitmask indicating visited nodes (1 << i for node i) +3. Initialize the queue with all possible starting nodes +4. For each state, explore all neighboring nodes +5. Use a visited map to avoid revisiting the same state +6. The target is reached when all nodes are visited (bitmask equals 2^n - 1) + +## Complexity + +- Time complexity: O(n * 2^n) +- Space complexity: O(n * 2^n) + +## Keywords + +- BFS +- Bitmask +- State-based Search +- Graph Traversal +- Shortest Path + +## Code + +```go +func shortestPathLength(graph [][]int) int { + if len(graph) == 1 { + return 0 + } + + type unit struct { + n int + mask int + } + newUnit := func(i, m int) *unit { + return &unit{ + n: i, + mask: m, + } + } + + visited := make([]map[int]bool, len(graph)) + queue := make([]*unit, 0) + for i := range graph { + mask := 1 << i + visited[i] = make(map[int]bool) + queue = append(queue, newUnit(i, mask)) + } + + target, ret := 1 << len(graph) - 1, 1 + for { + n := len(queue) + for _, cur := range queue { + for _, nb := range graph[cur.n] { + nextMask := cur.mask | (1 << nb) + if !visited[nb][nextMask] { + if nextMask == target { + goto RETURN + } + visited[nb][nextMask], queue = true, append(queue, newUnit(nb, nextMask)) + } + } + } + queue, ret = queue[n:], ret + 1 + } + +RETURN: + return ret +} +```