Fundamentals
The most commonly way of DFS is to call a helper function recursively. Due to the nature of recursion, we can instead use a Stack.
Keys and Rooms
Recursion
1
2
3
4
5
6
7
8
9
10
11
| public boolean canVisitAllRooms(List<List<Integer>> rooms) {
Set<Integer> visited = new HashSet<>();
dfs(rooms, 0, visited);
return visited.size() == rooms.size();
}
private void dfs(List<List<Integer>> rooms, int label, Set<Integer> visited) {
if (visited.add(label)) {
rooms.get(label).forEach(k -> dfs(rooms, k, visited));
}
}
|
Stack
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| public boolean canVisitAllRooms(List<List<Integer>> rooms) {
Set<Integer> visited = new HashSet<>();
set.add(0);
Deque<Integer> st = new ArrayDeque<>();
st.push(0);
while (!st.isEmpty()) {
int room = st.pop();
for (int key : rooms.get(room)) {
if (visited.add(key)) {
st.push(key);
}
}
}
return visited.size() == rooms.size();
}
|
For-loop
This is for a special case where every node in the directed graph has at most one destination node.
Longest Cycle in a Graph
1
2
3
4
5
6
7
8
9
| for (int i = 0; i < n; i++) {
// dfs until no destination node
for (int j = i, dist = 0; j >= 0; j = edges[i]) {
// or detects a loop
if (memo.containsKey(j)) {
return;
}
}
}
|
Again, because of the recursive nature, recursion problems can be solved by DFS:
Clone Graph
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| private Map<Integer, Node> map = new HashMap<>();
public Node cloneGraph(Node node) {
return dfs(node);
}
private Node dfs(Node node) {
if (node == null) {
return null;
}
if (map.containsKey(node.val)) {
return map.get(node.val);
}
Node copy = new Node(node.val, new ArrayList<>());
map.put(copy.val, copy);
node.neighbors.forEach(n -> copy.neighbors.add(dfs(n)));
return copy;
}
|
Pyramid Transition Matrix
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| private Map<String, Set<Character>> map;
public boolean pyramidTransition(String bottom, List<String> allowed) {
map = new HashMap<>();
for (String s : allowed) {
map.computeIfAbsent(s.substring(0, 2), k -> new HashSet<>()).add(s.charAt(2));
}
return dfs(bottom, "", 1);
}
private boolean dfs(String row, String nextRow, int index) {
if (row.length() == 1) {
return true;
}
if (nextRow.length() + 1 == row.length()) {
return dfs(nextRow, "", 1);
}
String key = row.substring(index - 1, index + 1);
if (map.containsKey(key)) {
for (Character c : map.get(key)) {
if (dfs(row, nextRow + c, index + 1)) {
return true;
}
}
}
return false;
}
|
Bottom-up
Time Needed to Inform All Employees
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| public int numOfMinutes(int n, int headID, int[] manager, int[] informTime) {
int minutes = 0;
for (int i = 0; i < n; i++) {
minutes = Math.max(minutes, dfs(i, manager, informTime));
}
return minutes;
}
// bottom-up, this is essentially DFS with DP
public int dfs(int i, int[] manager, int[] informTime) {
if (manager[i] != -1) {
informTime[i] += dfs(manager[i], manager, informTime);
// don't visit his manager twice
manager[i] = -1;
}
return informTime[i];
}
|
Tree
DFS a tree usually follow the pattern:
1
| int dfs(List<Integer>[] tree, int node, int parent, ...)
|
Create Components With Same Value
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
| private List<Integer>[] tree;
public int componentValue(int[] nums, int[][] edges) {
int n = nums.length;
this.tree = new List[n];
for (int i = 0; i < n; i++) {
tree[i] = new ArrayList<>();
}
for (int[] e : edges) {
tree[e[0]].add(e[1]);
tree[e[1]].add(e[0]);
}
int i = 1;
while (i < n) {
if (nums[i] != nums[i - 1]) {
break;
}
i++;
}
if (i == n) {
return n - 1;
}
int sum = 0, max = 0;
for (int num : nums) {
sum += num;
max = Math.max(max, num);
}
// factorization
// sum of each component >= max node value
for (int f = sum / max; f > 1; f--) {
if (sum % f == 0 && dfs(0, -1, nums, sum / f) == 0) {
return f - 1;
}
}
return 0;
}
private int dfs(int node, int parent, int[] nums, int componentSum) {
int sum = nums[node];
for (int i : tree[node]) {
if (i != parent) {
int val = dfs(i, node, nums, componentSum);
if (val < 0) {
return -1;
}
sum += val;
}
}
// not a valid path
if (sum > componentSum) {
return -1;
}
// if sum == componentSum, the component is complete
return sum % componentSum;
}
|
Pattern Signature
Number of Distinct Islands
1
2
3
4
5
6
| pattern.append(index);
for (int k = 0; k < DIRECTIONS.length; k++) {
int[] d = DIRECTIONS[k];
dfs(i + d[0], j + d[1], (char)('0' + k), pattern);
}
pattern.append('#');
|
Brute Force
Lexicographically Smallest String After Applying Operations
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
| private String min;
private int a, b;
private Set<String> visited = new HashSet<>();
public String findLexSmallestString(String s, int a, int b) {
this.min = s;
this.a = a;
this.b = b;
dfs(s);
return min;
}
private void dfs(String s) {
if (visited.add(s)) {
if (min.compareTo(s) > 0) {
min = s;
}
dfs(add(s));
dfs(rotate(s));
}
}
private String add(String s) {
char[] chars = s.toCharArray();
for (int i = 1; i < chars.length; i += 2) {
chars[i] = (char)((chars[i] - '0' + a) % 10 + '0');
}
return new String(chars);
}
privat String rotate(String s) {
int n = s.length();
return s.substring(n - b) + s.substring(0, n - b);
}
|
This solution includes all possibilities, and uses a Set as termination condition.
Lexicographical Numbers
Transitions
The Earliest and Latest Rounds Where Players Compete
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
| private int min = Integer.MAX_VALUE, max = Integer.MIN_VALUE;
public int[] earliestAndLatest(int n, int firstPlayer, int secondPlayer) {
dfs(firstPlayer, n - secondPlayer + 1, n, 1);
return new int[]{min, max};
}
// @param l length of [1, first player]
// @param r length of [second player, n]
private void dfs(int l, int r, int n, int round) {
if (l == r) {
min = Math.min(min, round);
max = Math.max(max, round);
return;
}
// makes sure l < r
if (l > r) {
int tmp = l;
l = r;
r = tmp;
}
// i players on the left win
for (int i = 1; i <= l; i++) {
// (l - i) palyers on the right win
// assumes second player is champion (+1)
// players in the next round: (n + 1) / 2
for (int j = l - i + 1; i + j <= Math.min(r, (n + 1) / 2); j++) {
// losers = (l - i) + (r - j)
if ((l - i) + (r - j) <= n / 2) {
dfs(i, j, (n + 1) / 2, round + 1);
}
}
}
}
|
Reverse
Bricks Falling When Hit
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
|
private static final int[][] DIRECTIONS = {{-1, 0}, {0, -1}, {1, 0}, {0, 1}};
private int[][] grid;
public int[] hitBricks(int[][] grid, int[][] hits) {
this.grid = grid;
int m = grid.length, n = grid[0].length;
// modifies the grid by decrementing the value of hit bricks
for (int[] h : hits) {
grid[h[0]][h[1]]--;
}
for (int i = 0; i < n; i++) {
dfs(0, i);
}
// reversely adding the erased bricks
int[] result = new int[hits.length];
for (int k = hits.length - 1; k >= 0; k--) {
int[] h = hits[k];
int i = h[0], j = h[1];
// if the new brick can be stable after being added,
// adds it
if (grid[i][j]++ == 0 && isStable(i, j)) {
// minus the brick itself
result[k] = dfs(i, j) - 1;
}
}
return result;
}
// returns number of stable bricks in (self + its subtree)
private int dfs(int i, int j) {
// now grid[i][j] means stable after erasing all hit bricks
if (i < 0 || i == grid.length || j < 0 || j == grid[0].length || grid[i][j] != 1) {
return 0;
}
// marks stable bricks
grid[i][j] = 2;
int count = 1;
for (int[] d : DIRECTIONS) {
count += dfs(i + d[0], j + d[1]);
}
return count;
}
private boolean isStable(int i, int j) {
// top row
if (i == 0) {
return true;
}
for (int[] d : DIRECTIONS) {
int r = i + d[0], c = j + d[1];
if (r >= 0 && r < grid.length && c >= 0 && c < grid[0].length && grid[r][c] == 2) {
return true;
}
}
return false;
}
|
+ BFS
Shortest Path in a Hidden Grid
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
|
private static final int[][] DIRECTIONS = {{-1, 0}, {0, -1}, {1, 0}, {0, 1}};
private static final char[] CHARS = {'U', 'L', 'D', 'R'};
private static final int N = 500;
private enum Cell {
UNVISITED(-1),
BLOCKED(0),
EMPTY(1),
TARGET(2);
private int value;
Cell(int value) {
this.value = value;
}
}
public int findShortestPath(GridMaster master) {
// 4 x grid, and places starting cell at the centor
Cell[][] grid = new Cell[2 * N][2 * N];
for (int i = 0; i < grid.length; i++) {
Arrays.fill(grid[i], Cell.UNVISITED);
}
// (N, N) is the start
dfs(N, N, master, grid);
Queue<int[]> q = new LinkedList<>();
q.offer(new int[]{N, N});
// marks visited as BLOCKED
grid[N][N] = Cell.BLOCKED;
// bfs
int distance = 0;
while (!q.isEmpty()) {
for (int i = q.size(); i > 0; i--) {
int[] node = q.poll();
for (int[] d : DIRECTIONS) {
int r = node[0] + d[0], c = node[1] + d[1];
if (grid[r][c] == Cell.TARGET) {
return distance + 1;
}
if (grid[r][c] != Cell.BLOCKED) {
q.offer(new int[]{r, c});
grid[r][c] = Cell.BLOCKED;
}
}
}
distance++;
}
return -1;
}
// explores the map
private void dfs(int i, int j, GridMaster master, Cell[][] grid) {
if (grid[i][j] != Cell.UNVISITED) {
return;
}
grid[i][j] = master.isTarget() ? Cell.TARGET : Cell.EMPTY;
for (int k = 0; k < 4; k++) {
int r = i + DIRECTIONS[k][0], c = j + DIRECTIONS[k][1];
if (master.canMove(CHARS[k])) {
master.move(CHARS[k]);
dfs(r, c, master, grid);
// moves back
master.move(CHARS[(k + 2) % 4]);
} else {
grid[r][c] = Cell.BLOCKED;
}
}
}
|
Contain Virus
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
|
private static final int[][] DIRECTIONS = {{-1, 0}, {0, -1}, {1, 0}, {0, 1}};
private int m, n;
// infected but not quarantined regions
private Queue<Region> pq = new PriorityQueue<>(Comparator.comparingInt(r -> -r.neighbors.size()));
public int containVirus(int[][] grid) {
this.m = grid.length;
this.n = grid[0].length;
int count = 0, day = 1;
add(grid, day);
while (!pq.isEmpty()) {
// finds the region that threatens the most neighbors
Region region = pq.poll();
count += region.walls;
// marks the region as quaratined (-1)
for (int cell : region.infected) {
grid[cell / n][cell % n] = -1;
}
// marks the neighbors as infected
day++;
while (!pq.isEmpty()) {
for (int cell : pq.poll().neighbors) {
grid[cell / n][cell % n] = day;
}
}
// enqueues remaining regions
add(grid, day);
}
return count;
}
private void add(int[][] grid, int day) {
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == day) {
Region region = new Region();
dfs(grid, i, j, region, day);
if (!region.neighbors.isEmpty()) {
pq.offer(region);
}
}
}
}
}
public void dfs(int[][] grid, int i, int j, Region region, int day) {
if (i < 0 || i == m || j < 0 || j == n || grid[i][j] == -1 || grid[i][j] > day) {
return;
}
if (grid[i][j] == 0) {
// this is a neighbor cell of the region
region.walls++;
region.neighbors.add(i * n + j);
} else {
// increments the cell to mark it as visited
// conceptually it proceeds to next day
grid[i][j]++;
region.infected.add(i * n + j);
for (int[] d : DIRECTIONS) {
dfs(grid, i + d[0], j + d[1], region, day);
}
}
}
class Region {
int walls;
// infected cells in the region
// neighbors of the infected region
Set<Integer> infected, neighbors;
Region() {
walls = 0;
infected = new HashSet<>();
neighbors = new HashSet<>();
}
}
|
Multi-dimension
Number of Valid Move Combinations On Chessboard
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
|
private static final int[][] DIRECTIONS = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}, {1, 1}, {1, -1}, {-1, -1}, {-1, 1}};
// board[p][i][j]: the steps for piece p to reach (i, j)
// if (i, j) is destination, its value is negative
private int[][][] board = new int[4][8][8];
public int countCombinations(String[] pieces, int[][] positions) {
return dfs(pieces, positions, 0);
}
public int dfs(String[] pieces, int[][] positions, int p) {
if (p >= pieces.length) {
return 1;
}
int count = 0;
for (int i = 0; i < DIRECTIONS.length; i++) {
if ((i < 4 && pieces[p].equals("bishop")) || (i >= 4 && pieces[p].equals("rook"))) {
continue;
}
boolean isBlocked = false;
// ensures only one direction marks the start position as step 1
int step = Math.min(2, count + 1);
while (!isBlocked) {
int r = (positions[p][0] - 1) + (step - 1) * DIRECTIONS[i][0];
int c = (positions[p][1] - 1) + (step - 1) * DIRECTIONS[i][1];
if (r < 0 || r >= board[0].length || c < 0 || c >= board[0][0].length) {
break;
}
boolean canStop = true;
for (int j = 0; j < p; j++) {
// no other pieces stopped at this position, and
// other pieces already passed by this position (or haven't reached this position (== 0))
canStop &= board[j][r][c] >= 0 && board[j][r][c] < step;
// another piece stopped at this position with <= steps to get there, or
// another piece passed by this position at the same time
isBlocked |= (board[j][r][c] < 0 && -board[j][r][c] <= step)
|| board[j][r][c] == step;
}
if (canStop) {
// if this piece stops at current position
// marks destination by negative step
board[p][r][c] = -step;
count += dfs(pieces, positions, p + 1);
}
board[p][r][c] = step++;
}
// clears board to prepare for a new direction of this piece
board[p] = new int[8][8];
}
return count;
}
|