(算法笔试) Leetcode101之搜索算法

1.深度优先搜索dfs

对于树而言,遍历某个节点优先遍历其孩子节点而不是其兄弟节点。
dfs是考虑所有可能,会重复计算,有时需要记忆化存储(动规)、或者用于树形dp。
其可以通过以迭代的方式来实现。

695. 岛屿的最大面积 – 中等

求最大的岛屿面积,相邻的1的土地为同一个岛屿。
在这里插入图片描述

输入:grid = [[0,0,1,0,0,0,0,1,0,0,0,0,0],[0,0,0,0,0,0,0,1,1,1,0,0,0],[0,1,1,0,1,0,0,0,0,0,0,0,0],[0,1,0,0,1,1,0,0,1,0,1,0,0],[0,1,0,0,1,1,0,0,1,1,1,0,0],[0,0,0,0,0,0,0,0,0,0,1,0,0],[0,0,0,0,0,0,0,1,1,1,0,0,0],[0,0,0,0,0,0,0,1,1,0,0,0,0]]
输出:6
解释:答案不应该是 11 ,因为岛屿只能包含水平或垂直这四个方向上的 1 。
/**
	可以使用dfs,也可以使用bfs
	1. 递归写法
	2. 栈写法
**/
class Solution {
    public int maxAreaOfIsland(int[][] grid) {
        int max = 0;
    	for (int i = 0; i < grid.length; i++) {
			for (int j = 0; j < grid[i].length; j++) {
				if (grid[i][j] == 1) max = Math.max(max, countArea(grid, i, j));
			}
		}
    	return max;
    }

    private int countArea(int[][] grid, int i, int j) {
		if(i < 0 || j < 0 || i >= grid.length || j >= grid[i].length || grid[i][j] == 0) return 0;
		int ans = 1;
		grid[i][j] = 0;
		
		int[] direction = {-1, 0, 1, 0, -1};
		for (int k = 0; k < 4; k++) {
			int row = i + direction[k], colomn = j + direction[k+1];
			ans += countArea(grid, row, colomn);
		}
		return ans;
	}
}


/***/
class Solution {
    public int maxAreaOfIsland(int[][] grid) {
		int max = 0, m = grid.length -  1, n = grid[0].length - 1;
		Deque<Integer> xDeque = new LinkedList<>();
		Deque<Integer> yDeque = new LinkedList<>();
    	for (int i = 0; i <= m; i++) {
			for (int j = 0; j <= n; j++) {
				int area = 0;
				if (grid[i][j] == 1) {
					xDeque.push(i);
					yDeque.push(j);
					while (!xDeque.isEmpty()) {
						int x = xDeque.poll(), y = yDeque.poll();
						if (x < 0 || y < 0 || x > m || y > n || grid[x][y] == 0) continue;
						grid[x][y] = 0;
						++area;
						int[] direction = {-1, 0, 1, 0, -1};
						for (int k = 0; k < 4; k++) {
							int x1 = x + direction[k], y1 = y + direction[k+1];
							xDeque.push(x1);
							yDeque.push(y1);
						}
					}
				}
				max = Math.max(max, area);
			}
		}
		return max;
    }
}

547. 省份数量 – 中等

有n个城市,只要直接或间接相邻的城市就算一个省,求这n个城市中省份数量。
在这里插入图片描述

输入:isConnected = [[1,1,0],[1,1,0],[0,0,1]]
输出:2
    [1,1,0],[1,1,0],[0,0,1] 分别表示 城市1 与 城市 1,2 相连
                                	城市2 与 城市 1,2 相连
                                	城市3 与 城市 1,3 相连
/**
	1. dfs: 如果该城市没有被访问过,省份+1, 同时访问其相邻城市。
	2. bfs类似
	3. 并查集:parent[i] 记录每个节点的祖先,每个祖先代表一个省  (即:求连通分量个数)
**/
class Solution {
    boolean[] isVisted;
    public int findCircleNum(int[][] isConnected) {
    	int n = isConnected.length;
    	isVisted = new boolean[n];
    	
    	int res = 0;
    	for(int i = 0; i < n; ++i) {
    		if (!isVisted[i]) {
				++res;
				dfs(isConnected, i);
			}
    	}
    	return res;
    }

	private void dfs(int[][] isConnected, int i) {
		isVisted[i] = true;
		for (int j = 0; j < isConnected[i].length; j++) {
			int val = isConnected[i][j];
			if (val == 1 && !isVisted[j]) {
				dfs(isConnected, j);
			}
		}
	}
}

/****/
class Solution {
    public int findCircleNum(int[][] isConnected) {
		int n = isConnected.length; 
		int[] parent = new int[n];
		for (int i = 0; i < n; i++) {
			parent[i] = i;
		}
		
		for (int i = 0; i < n; i++) {
			for (int j = 0; j < n; j++) {
				if (isConnected[i][j] == 1) {
					union(parent, i, j);
				}
			}
		}
		
		int res = 0;
		for (int i = 0; i < n; i++) {
			if (parent[i] == i) ++res;
		}
		return res;
    }

	private void union(int[] parent, int i, int j) {
		parent[find(parent, j)] = find(parent, i);  // j的祖先挂到i祖先下面
	}

	private int find(int[] parent, int i) {
		while (parent[i] != i) i = parent[i];
		return i;
	}
}

417. 太平洋大西洋水流问题 – 中等

求m*n的岛屿中,雨水同时可以流入太平洋和大西洋的岛屿。高的岛屿的雨水可以流到不高于自己的相邻岛屿上,与海相邻的岛屿雨水可以直接流入海里。岛屿的左上相邻太平洋,右下相邻大西洋。
在这里插入图片描述

输入: heights = [[1,2,2,3,5],[3,2,3,4,4],[2,4,5,3,1],[6,7,1,4,5],[5,1,1,2,4]]
输出: [[0,4],[1,3],[1,4],[2,2],[3,0],[3,1],[4,0]]
如图所示:黄色部分的岛屿上的雨水可以同时流入到太平洋和大西洋中。
/**
	从四个边界出发dfs。
	例如: 左边界一定可以流入太平洋,那么与左边界相邻的且比它高的一定也可以流入太平洋。
    	如果一个格子同时可以流入太平洋和大西洋,那么加入到结果中。
**/
class Solution {
    public List<List<Integer>> pacificAtlantic(int[][] heights) {
    	int m = heights.length, n = heights[0].length;
    	boolean[][] p2a = new boolean[m][n];
    	boolean[][] a2p = new boolean[m][n];
    	
    	for (int i = 0; i < m; i++) {
			dfs(heights, i, 0, a2p);
			dfs(heights, i, n-1, p2a);
		}
    	for (int i = 0; i < n; i++) {
    		dfs(heights, 0, i, a2p);
    		dfs(heights, m-1, i, p2a);
		}
    	
    	List<List<Integer>> res = new ArrayList<>();
    	for (int i = 0; i < m; i++) {
			for (int j = 0; j < n; j++) {
				if (p2a[i][j] && a2p[i][j]) {
					List<Integer> ans = new ArrayList<>();
					Collections.addAll(ans, i, j);
					res.add(ans);
				}
			}
		}
    	return res;
    }

    private void dfs(int[][] heights, int i, int j, boolean[][] res) {
		if (res[i][j]) return;
		res[i][j] = true;
		
		int[] direction = {-1, 0, 1, 0, -1};
		for (int k = 0; k < 4; k++) {
			int x1 = i + direction[k], y1 = j + direction[k+1];
			if (x1 < 0 || y1 < 0 || x1 >= res.length || y1 >= res[0].length) continue;
			if (heights[x1][y1] >= heights[i][j]) {
				dfs(heights, x1, y1, res);
			}
		}
	}
}

回溯法

在搜索过程中,状态会一直改变,回溯是相当于同一个位置,使得其状态一样。也就是在dfs方法执行返回到上一级时,需要返回到上一级的初始状态。

46. 全排列 – 中等

给一个不含重复元素的数组,返回其全排列。

输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
/**
	回溯法:对于i位置,其可取的范围是 [i,n-1] 每次交换一个位置,每次交换需要返回原状态
*/
class Solution {
    public List<List<Integer>> permute(int[] nums) {
    	int n = nums.length;
    	List<List<Integer>> res = new ArrayList<>();
    	List<Integer> path = new ArrayList<>();
    	for (int i = 0; i < nums.length; i++) {
			path.add(nums[i]);
		}
    	backTrack(res, n, 0, path);
    	return res;
    }

	private void backTrack(List<List<Integer>> res, int n, int cur, List<Integer> path) {
		if (cur == n) {
			res.add(new ArrayList<>(path));
            return;
		}
		
		for (int i = cur; i < n; i++) {
			Collections.swap(path, cur , i);
			backTrack(res, n, cur + 1, path);
			Collections.swap(path, cur , i);
		}
	}
}

77. 组合 – 中等

给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。

输入:n = 4, k = 2
输出:	[[2,4], [3,4], [2,3], [1,2], [1,3], [1,4]]
解释:[1,4] 和 [4,1] 是同一种组合
/**
	组合与排列不同:
    	排列:[1,4] [4,1] 是不同排列
    	组合: [1,4] [4,1] 是同一种组合
    	-- 可以对原数组进行一次升序排序,防止出现重复组合
    	-- 重点:这里是有序的所以不用排序
	组合的回溯问题:在i位置的可选数据范围 [i, n]
    	每次选择了一个数,之后就不会重复选择了,回溯后把它给删了  
*/
class Solution {

    public List<List<Integer>> combine(int n, int k) {
    	List<List<Integer>> res = new ArrayList<>();
    	List<Integer> path = new ArrayList<>();
    	back_Track(res, n, k, 1, path);
    	return res;
    }

    private void back_Track(List<List<Integer>> res, int n, int k, int cur, List<Integer> path) {
    	if (path.size() + n - cur + 1 < k) return;    // 剪枝,如果选择剩下所有元素达不到k个数,结束
    	if (path.size() == k) {
			res.add(new ArrayList<>(path));
			return;
		}
    	
    	for (int i = cur; i <= n; i++) {
			path.add(i);
			back_Track(res, n, k, i + 1, path);
			path.remove(path.size() - 1);
		}
    }
}

79. 单词搜索 – 中等

判断一个单词是否在二维字符网格中,同一单元格内字母不允许被重复使用。
在这里插入图片描述

输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED"
输出:true
/**
	dfs+回溯。
	用tag数组记录每个字符是否被访问,如果没有找到答案回溯,置为未访问。
**/
class Solution {
    int m,n;
    public boolean exist(char[][] board, String word) {
    	m = board.length;
    	n = board[0].length;
    	boolean[][] tag = new boolean[m][n];
    	
    	for (int i = 0; i < m; i++) {
			for (int j = 0; j < n; j++) {
				if (board[i][j] == word.charAt(0)) {
					if (back(board, word, tag, 0, i , j)) return true;
				}
			}
		}
    	return false;
    }

    private boolean back(char[][] board, String word, boolean[][] tag, int idx, int i, int j) {
		tag[i][j] = true;
		if (idx == word.length() - 1) return true;
        
		int[] direction = {-1, 0, 1, 0, -1};
		for (int k = 0; k < 4; k++) {
			int x = i + direction[k], y = j + direction[k+1];
			if (x < 0 || y < 0 || x >= board.length || y >= board[0].length 
                || board[x][y] != word.charAt(idx + 1) || tag[x][y]) continue;
			if (back(board, word, tag, idx + 1, x, y)) return true;
		}
		tag[i][j] = false;
		return false;
	}
}

51. N 皇后 – 困难

同一行同一列同一斜线的皇后可以互相攻击,求在n*n的棋盘上放n个皇后,使得皇后之间不互相攻击的所有可能。
在这里插入图片描述

输入:n = 4
输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
解释:如图,2种情况,其中.表示不放皇后,Q表示放皇后。
/**
	回溯:一行一行添加皇后,如果该行的某个位置可以放皇后
    	那么将皇后所在的列,左对角线 和 有对角线 设置为true,表示不可以在放了
    	注:左对角线的 row + col 是定值
        	右对角线 row-col 是定值, row-col+n-1 将所有值映射到 正值

**/
class Solution {
    boolean[] colomn, lDiagonal, rDiagonal;
	public List<List<String>> solveNQueens(int n) {
    	List<List<String>> res = new ArrayList<>();
    	List<String> path = new ArrayList<>();
    	colomn = new boolean[n]; lDiagonal = new boolean[2*n]; rDiagonal = new boolean[2*n];
    	
    	dfs(res, 0, path, n-1);
    	return res;
    }

    private void dfs(List<List<String>> res, int row, List<String> path, int tail) {
		if (row == tail + 1) {
			res.add(new ArrayList<>(path));
			return;
		}
		
		for (int col = 0; col <= tail; ++col) {
			if (colomn[col] || lDiagonal[row + 1 + col] || rDiagonal[tail + row + 1 - col]) continue;
			colomn[col] = lDiagonal[row + 1 + col] = rDiagonal[tail + row + 1 - col] = true;
			path.add(createString(col, tail));
			dfs(res, row+1 , path, tail);
			path.remove(path.size() - 1);
			colomn[col] = lDiagonal[row + 1 + col] = rDiagonal[tail + row + 1 - col] = false;
		}	
	}

	private String createString(int col, int tail) {
		StringBuilder sb = new StringBuilder();
		for (int j = 0; j <= tail; j++) {
			if (j == col) sb.append('Q');
			else sb.append('.');
		}
		return sb.toString();
	}
}

广度优先搜索

对于树而言,优先搜索其兄弟节点,然后搜索其孩子节点。可以使用队列来迭代实现。

934. 最短的桥 – 中等

连接两个岛屿的最短长度。

输入:grid = [[0,1],[1,0]]
输出:1
解释:把其中一个0变成1,两个岛屿就相连了
/**
	dfs+bfs:
    	找到第一个岛并将其所有元素存在队列中,然后弹出队列中元素进行bfs搜索。
	第一次找第一个岛时,将其元素涂成2。
	遍历时遇到0,就将其涂成2,遇到1返回其层级。

**/
class Solution {
    Queue<Integer> x_i = new LinkedList<>();
	Queue<Integer> y_j = new LinkedList<>();
    public int shortestBridge(int[][] grid) {
    	int n = grid.length;
        
    	OUT:
    	for (int i = 0; i < n; i++) {
			for (int j = 0; j < n; j++) {
				if (grid[i][j] == 1) {
					dfsIs(grid, i, j);
					break OUT;
				}
			}
		}
    	
    	int level = 0;
        // System.out.println(x_i);
    	while(!x_i.isEmpty()) {
    		int size = x_i.size();
    		for (int i = 0; i < size; i++) {
				int x = x_i.poll(), y = y_j.poll();
				
				int[] direction = {-1, 0, 1, 0, -1};
				for (int k = 0; k < 4; k++) {
					int x1 = x + direction[k], y1 = y + direction[k+1];
                    if (x1 < 0 || y1 < 0 || x1 >= n || y1 >= n) continue;
					if (grid[x1][y1] == 1) return level;
					else if (grid[x1][y1] == 0) {
                        grid[x1][y1] = 2;
						x_i.add(x1);
						y_j.add(y1);
					} 
				}
			}
            ++level;
    	}
    	return level;
    }

    // 将第一个岛中所有1图成2
	private void dfsIs(int[][] grid, int i, int j) {
		if (grid[i][j] == 2) return;
		grid[i][j] = 2;
		x_i.add(i);
		y_j.add(j);
        
		int[] direction = {-1, 0, 1, 0, -1};
		for (int k = 0; k < 4; k++) {
			int x = i + direction[k], y = j + direction[k+1];
			if (x < 0 || y < 0 || x >= grid.length 
					|| y >= grid[0].length || grid[x][y] != 1) continue;
			dfsIs(grid, x, y);
		}
	}
}

126. 单词接龙 II – 困难

给定一个单词和词典,单词每次可以修改一个字符得到一个新单词,如果新单词在词典中,这次修改是可行的,输出单词到目标单词的最短转换序列,可以不存在转换序列。

输入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"]
输出:[["hit","hot","dot","dog","cog"],["hit","hot","lot","log","cog"]]
解释:存在 2 种最短的转换序列:
	"hit" -> "hot" -> "dot" -> "dog" -> "cog"	"hit" -> "hot" -> "lot" -> "log" -> "cog"

单BFS超时,需要双向同时BFS,太麻烦了,懒得思考。

练习

130. 被围绕的区域 – 中等

给定一个m*n的矩阵,将所有被X包围的O变成X。
在这里插入图片描述

输入:board = [["X","X","X","X"],["X","O","O","X"],["X","X","O","X"],["X","O","X","X"]]
输出:[["X","X","X","X"],["X","X","X","X"],["X","X","X","X"],["X","O","X","X"]]
解释:与边界O相邻的O不会被修改为X
/**
	思路:把与边界O相邻的所有改为A,然后遍历整个board,将O变为X,A变为O
**/
class Solution {
    public void solve(char[][] board) {
    	int m = board.length, n = board[0].length;
    	for (int i = 0; i < m; i++) {
			if (board[i][0] == 'O') dfsFindX(board, i, 0);
			if (board[i][n-1] == 'O') dfsFindX(board, i, n-1);
		}
    	for (int i = 0; i < n; i++) {
			if (board[0][i] == 'O') dfsFindX(board, 0, i);
			if (board[m-1][i] == 'O') dfsFindX(board, m-1, i);
		}
    	for (int i = 0; i < m; i++) {
			for (int j = 0; j < n; j++) {
				if (board[i][j] == 'O') board[i][j] = 'X';
				else if (board[i][j] == 'A') board[i][j] = 'O';
			}
		}
    }

    private void dfsFindX(char[][] board, int i, int j) {
		board[i][j] = 'A';
		int[] direction = {-1, 0, 1, 0, -1};
		for (int k = 0; k < 4; k++) {
			int x = i + direction[k], y = j + direction[k+1];
			if (x < 0 || y < 0 || x >= board.length || y >= board[0].length || board[x][y] != 'O') continue;
			dfsFindX(board, x, y);
		}
	}
}

257. 二叉树的所有路径 – 简单

输出根节点到叶子节点的所有路径。
在这里插入图片描述

输入:root = [1,2,3,null,5]
输出:["1->2->5","1->3"]
class Solution {
    public List<String> binaryTreePaths(TreeNode root) {
    	List<String> res = new ArrayList<>();
    	dfsTree(res, root, new String());
    	return res;
    }

    private void dfsTree(List<String> res, TreeNode root, String s) {
		s += root.val;
		if (root.left == null && root.right == null) {
			res.add(new String(s));
			return;
		}
		
		s += "->";
		int len = s.length();
		if (root.left != null) dfsTree(res, root.left, s);
		if (root.right != null) dfsTree(res, root.right, s.substring(0, len));
	}
}

47. 全排列 II – 中等

返回可包含重复数的全排列。

输入:nums = [1,1,2]
输出:	[[1,1,2], [1,2,1], [2,1,1]]
/**
	思路和不包含重复数的全排列类似。
	只是在同一个位置不能取相同的数。
    	如 [1 1 2]  第一个位置可取第一个1 而不可以取第2个1  --- 自己规定的一种方式
    	可以用set来记录当前取值集合。
**/
class Solution {
    public List<List<Integer>> permuteUnique(int[] nums) {
    	List<List<Integer>> res = new ArrayList<>();
    	List<Integer> path = new ArrayList<>();
    	int n = nums.length;
    	for (int i = 0; i < n; i++) {
			path.add(nums[i]);
		}
    	dfsBack(res, path, 0, n);
    	return res;
    }

    private void dfsBack(List<List<Integer>> res, List<Integer> path, int loc, int n) {
		if (loc == n) {
			res.add(new ArrayList<>(path));
			return;
		}
		
		Set<Integer> hasVisted = new HashSet<>();	// 去重
		for (int i = loc; i < n; i++) {
			if (hasVisted.contains(path.get(i))) continue;
            hasVisted.add(path.get(i));
			Collections.swap(path, i, loc);
			dfsBack(res, path, loc + 1, n);
			Collections.swap(path, i, loc);
		}
	}
}

40. 组合总和 II – 中等

找出数组中所有和为目标值的组合。(不能包含重复的组合)

输入: candidates = [10,1,2,7,6,1,5], target = 8,
输出: [[1,1,6], [1,2,5], [1,7], [2,6]]
/**
	每个位置不能选重复数,但还存在另一种重复 如上面例子中 [1,7]  和 [7,1]
	选择顺序是不同的,但组合是相同。
	因此在选择前对数组进行排序。
**/
class Solution {
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        Arrays.sort(candidates);	// 增加一个排序
    	List<List<Integer>> res = new ArrayList<>();
    	List<Integer> path = new ArrayList<>();
    	dfsFind(res, candidates, target, path, 0, target);
    	return res;
    }

    private void dfsFind(List<List<Integer>> res, int[] candidates, int target, List<Integer> path, int idx, int val) {
		if (val == 0) {
			res.add(new ArrayList<>(path));
			return;
		}
		if (val < 0 || idx >= candidates.length) return;
		
		Set<Integer> isVisited = new HashSet<>();
		for (int i = idx; i < candidates.length; i++) {
			if (isVisited.contains(candidates[i])) continue;
            isVisited.add(candidates[i]);
			path.add(candidates[i]);
			dfsFind(res, candidates, target, path, i + 1, val - candidates[i]);
			path.remove(path.size() - 1);
		}
	}
}

37. 解数独 – 困难

给定一个9宫格数组,求解该数独,填入数字使得其满足数独规则。
在这里插入图片描述
在这里插入图片描述

输入:board = [["5","3",".",".","7",".",".",".","."],["6",".",".","1","9","5",".",".","."],[".","9","8",".",".",".",".","6","."],["8",".",".",".","6",".",".",".","3"],["4",".",".","8",".","3",".",".","1"],["7",".",".",".","2",".",".",".","6"],[".","6",".",".",".",".","2","8","."],[".",".",".","4","1","9",".",".","5"],[".",".",".",".","8",".",".","7","9"]]
输出:[["5","3","4","6","7","8","9","1","2"],["6","7","2","1","9","5","3","4","8"],["1","9","8","3","4","2","5","6","7"],["8","5","9","7","6","1","4","2","3"],["4","2","6","8","5","3","7","9","1"],["7","1","3","9","2","4","8","5","6"],["9","6","1","5","3","7","2","8","4"],["2","8","7","4","1","9","6","3","5"],["3","4","5","2","8","6","1","7","9"]]
/**
	这里和n皇后类似,除了考虑每行每列,还要考虑9宫格里面的数。
	不同之处:n皇后问题,每一行只要选择一个皇后位置即可,其它位置不需要考虑,最后一行结束即可返回。
    	而数独问题,每一行所有空位置都要考虑,因此回溯问题不等同。
	这里可以在遍历时,将每个空格记录在List中,用pos作为这是处理的第pos个空格,且每个空格取值
	在[1,9]之间,如果 pos = List.size() 处理完成,结束。
**/
class Solution {
    boolean[][] row, col;
	boolean[][][] square;
	boolean flag = false;
	List<int[]> space;
    public void solveSudoku(char[][] board) {
    	int n = board.length;
    	
    	row = new boolean[9][9];
    	col = new boolean[9][9];
    	square = new boolean[3][3][9];
    	space = new ArrayList<>();
    	
    	for (int i = 0; i < n; i++) {
			for (int j = 0; j < n; j++) {
				if (board[i][j] != '.') {
					int cur = board[i][j] - '1';
					row[i][cur] = col[j][cur] = square[i/3][j/3][cur] = true;
				} else {
					space.add(new int[] {i, j});
				}
			}
		}
    	
    	dfsBoard(board, 0);
    }

    private void dfsBoard(char[][] board, int pos) {
		if (pos == space.size()) {
			flag = true;
			return;
		}
		
		int x = space.get(pos)[0], y = space.get(pos)[1];
		
		for (int val = 0; val < 9 && !flag; ++val) {
			if (!row[x][val] && !col[y][val] && !square[x/3][y/3][val]) {
				row[x][val] = col[y][val] = square[x/3][y/3][val] = true; board[x][y] = (char) (val + 1 + '0');
				dfsBoard(board, pos+1);
				row[x][val] = col[y][val] = square[x/3][y/3][val] = false; 
			}
		}
	}
}

310. 最小高度树 – 中等

给定一个树,选择一个节点作为根节点,使得其树的高度最小,返回所有可能的根节点。(高度 = 根到叶边数)
在这里插入图片描述

输入:n = 6, edges = [[3,0],[3,1],[3,2],[3,4],[5,4]]
输出:[3,4]
解释:n表示6个节点, edge[0] = [3,0] 表示 3,0 相连, edge[1] = [3,1] 表示 3,1 相连
    这里只有两种选择,即选择3,4作为根节点,此时最小高度为2
/**
	常规做法:以每个节点作为根节点,求其高度,可以用当前最小高度来剪枝
	优化:寻找最长路径,返回其中间一个或两个节点
    	最长路径寻找:以任意点为起点 (以0为例),找到距其最远节点u;
        	在从u出发,找到距其最远节点v,那么 uv这里路径是最长路径。
    	这里可以在处理最长路径时,增加一个parent 记录其父节点,便于搜索该路径。
	另一种思路:不断删除度为1的节点
**/
class Solution {
    public List<Integer> findMinHeightTrees(int n, int[][] edges) {
    	List<Integer> res = new ArrayList<>();
    	List<Integer>[] tree = new ArrayList[n];
    	for (int i = 0; i < tree.length; i++) {
			tree[i] = new ArrayList<>();
		}
    	for (int i = 0; i < edges.length; i++) {
			int x = edges[i][0], y = edges[i][1];
			tree[x].add(y);
			tree[y].add(x);
		}

    	int[] parent = new int[n];
        for (int i = 0; i < n; ++i) parent[i] = i;

    	int u = findMaxFarNode(tree, parent, 0);
    	int v = findMaxFarNode(tree, parent, u);
    	List<Integer> path = new ArrayList<>();
    	while (v != u) {
    		path.add(v);
    		v = parent[v];
    	}
    	path.add(u);
    	res.add(path.get(path.size() / 2));
    	if (path.size() % 2 == 0) {
			res.add(path.get(path.size() / 2 - 1));
		}
    	return res;
    }

	private int findMaxFarNode(List<Integer>[] tree, int[] parent, int node) {
		Queue<Integer> queue = new LinkedList<>();
    	queue.add(node);
    	boolean[] hasVisited = new boolean[tree.length];
    	int resNode = -1;
    	while (!queue.isEmpty()) {
    		int size = queue.size();
    		resNode = queue.peek();
    		for (int i = 0; i < size; i++) {
				int curNode = queue.poll();
				hasVisited[curNode] = true;
				for (int nextNode : tree[curNode]) {
					if (hasVisited[nextNode]) continue;
					parent[nextNode] = curNode;  // 记录每个节点的父亲节点
					queue.add(nextNode);
				}
			}
    	}
		return resNode;
	}
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值