深度优先搜索 和 广度优先搜索 是两种最常见的优先搜索方法,它们被广泛应用于图和树等结构中搜索。
深度优先搜索(depth-first search,DFS)在搜索到一个新的节点时,立即对该节点进行遍历;因此遍历需要用先入后出的栈实现,也可以用通过与栈等价的递归实现。
对于树结构而言,由于总是对新节点调用遍历,因此看起来向着更“深”的方向前进。
深度优先搜索也可以用来检测环路:记录每个遍历过的节点的父节点,若一个节点被再次遍历且父节点不同,说明有环。
有时可能需要对已经搜索过的节点进行标记,防止在遍历时重复搜索某个节点,这种做法叫做状态记录或记忆化。
一般来说,深度优先搜索类型的题可以分为主函数和辅函数。
当然,也可以使用栈实现深度优先搜索,但因为递归和栈的原理相同,而递归相对容易实现,因此刷题时更推荐递归写法。
不过在实际工程中,栈可能是最好的选择。一是因为便于理解,二是因为不易出现递归栈满的情况。
题解
代码
class Solution {
public:
int maxAreaOfIsland(vector<vector<int>>& grid) {
// 定义方向数组
vector<vector<int>> dir{{0,1}, {0,-1}, {1,0}, {-1,0}};
int m = grid.size(), n = m ? grid[0].size() : 0;
int local_area , area = 0, x, y;
for(int i = 0; i < m; ++i){
for(int j = 0; j < n; ++j){
if(grid[i][j]){
local_area = 1;
grid[i][j] = 0; // 确保不会重复搜索
stack<pair<int, int>> island;
island.push({i, j});
while(!island.empty()){
auto [r, c] = island.top();
island.pop();
for(int k = 0; k < 4; ++k){
// 遍历 4 个方向
x = r + dir[k][0], y = c + dir[k][1];
if(x>=0 && x <= m-1 && y>=0 && y<=n-1 && grid[x][y] == 1){
grid[x][y] = 0;
island.push({x, y});
local_area++;
}
}
}
area = max(area, local_area);
}
}
}
return area;
}
};
收获
题解
主函数遍历 grid 的每一个格子,判断是否可以搜索,如果可以的话,就调用辅函数 dfs 进行深度优先搜索;
辅函数 dfs 负责进行深度优先搜索。首先,递归结束的条件是: grid[i][j] == 0,那么不会再增加新的面积,返回 0。
如果递归不会结束,那么将 grid[i][j] 置为 0,防止重复搜索。
接着一样遍历该位置的四个方向,进行边界判断以及确定下一个位置是否也是陆地,如果是的话,就对该位置再次进行递归。
代码
class Solution {
public:
vector<vector<int>> dir{{0,1}, {0,-1}, {1,0}, {-1,0}};
int maxAreaOfIsland(vector<vector<int>>& grid) {
int m = grid.size(), n = m ? grid[0].size() : 0;
int area = 0;
for(int i = 0; i < m; ++i){
for(int j = 0; j < n; ++j){
if(grid[i][j]){
area = max(area, dfs(grid, i, j));
}
}
}
return area;
}
int dfs(vector<vector<int>>& grid, int i, int j){
if(grid[i][j] == 0) return 0;
grid[i][j] = 0;
int x, y, area = 1;
for(int k = 0; k < 4; ++k){
x = i + dir[k][0], y = j + dir[k][1];
if(x >= 0 && x < grid.size() && y >= 0 && y < grid[0].size() && grid[x][y]){
area += dfs(grid, x, y);
}
}
return area;
}
};
题解
代码
class Solution {
public:
int maxAreaOfIsland(vector<vector<int>>& grid) {
int m = grid.size(), n = m ? grid[0].size() : 0;
int area = 0;
for(int i = 0; i < m; ++i){
for(int j = 0; j < n; ++j){
if(grid[i][j]){
area = max(area, dfs(grid, i, j));
}
}
}
return area;
}
int dfs(vector<vector<int>>& grid, int i, int j){
if(i<0 || i >= grid.size() || j<0 || j >= grid[0].size() || grid[i][j] ==0) return 0;
grid[i][j] = 0;
return 1 + dfs(grid, i+1, j) + dfs(grid, i-1, j) + dfs(grid, i, j+1) + dfs(grid, i, j-1);
}
};
题解
ans++
;代码
class Solution {
public:
int findCircleNum(vector<vector<int>>& isConnected) {
int ans = 0;
int n = isConnected.size();
vector<bool> visited(n, false); // 标记这个点是否被访问过
for(int i=0; i<n; ++i){
if(!visited[i]){
dfs(isConnected, i, visited);
ans++;
}
}
return ans;
}
void dfs(vector<vector<int>>& isConnected, int i, vector<bool>& visited){
visited[i] = true;
for(int k=0; k<isConnected.size(); ++k){
// 该点未被访问过,且两个点之间存在连接
if(!visited[k] && isConnected[i][k]==1){
dfs(isConnected, k, visited);
}
}
}
};
收获
思路
can_reach_p
和 can_reach_a
,它们分别记录两个大洋能够到达的位置;heights
的第一列和最后一列,分别对应两个大洋;同理,按列遍历 heights
的第一行和最后一行,以它们作为起点进行搜索。代码
class Solution {
public:
vector<vector<int>> dir = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
vector<vector<int>> pacificAtlantic(vector<vector<int>>& heights) {
int m = heights.size(), n = heights[0].size();
vector<vector<bool>> can_reach_p(m, vector<bool>(n, false));
vector<vector<bool>> can_reach_a(m, vector<bool>(n, false));
vector<vector<int>> ans;
for(int i=0; i<m ;++i){
dfs(heights, i, 0, can_reach_p);
dfs(heights, i, n-1, can_reach_a);
}
for(int j=0; j<n; ++j){
dfs(heights, 0, j, can_reach_p);
dfs(heights, m-1, j, can_reach_a);
}
for(int i=0; i<m; ++i){
for(int j=0; j<n; ++j){
if(can_reach_a[i][j] && can_reach_p[i][j]){
ans.push_back(vector<int> {i, j});
}
}
}
return ans;
}
void dfs(vector<vector<int>>& heights, int i, int j, vector<vector<bool>>& can_reach){
if(can_reach[i][j]) return ;
can_reach[i][j] = true;
int x, y;
for(int k=0; k<4; ++k){
int x = i + dir[k][0], y = j + dir[k][1];
if(x >= 0 && x < heights.size() && y >= 0 && y < heights[0].size() && heights[i][j] <= heights[x][y]){
dfs(heights, x, y, can_reach);
}
}
}
};
收获
深度优先搜索
思想:从一个位置出发,沿着一条路一直搜索,直到这条路没办法走通再返回上一层,继续搜索。
关键在于:
visited
)或多个数组( can_reach_p
和 can_reach_a
);回溯法是优先搜索的一种特殊情况,又称为试探法,常用于需要记录节点状态的深度优先搜索。通常来说,排列、组合、选择类问题,使用回溯法比较方便。
回溯法的核心是回溯。在搜索到某一节点的时候,如果我们发现当前节点(及其子节点)不是需求目标,我们回退到原来的节点继续搜索,并且把当前节点修改的状态还原。
在具体的写法上,它与普通的深度优先搜索一样,都有 「修改当前节点状态」-> 「递归子节点」的步骤,不过还多了回溯的步骤,变成了「修改当前节点状态」-> 「递归子节点」-> 「回改当前节点状态」。
回溯法有两个诀窍,一是按引用传状态,二是所有的状态修改在递归完成后回改。
回溯法修改一般有两种情况,一种是修改最后一位输出,比如排列组合;一种是修改访问标记,比如矩阵里搜字符串。
代码
class Solution {
public:
vector<vector<int>> permute(vector<int>& nums) {
vector<vector<int>> ans;
backtracking(nums, 0, ans);
return ans;
}
void backtracking(vector<int> &nums, int level, vector<vector<int>> &ans){
// l 表示当前选了几个数
if(level == nums.size()){
ans.push_back(nums);
return ;
}
for(int i=level; i<nums.size(); ++i){
swap(nums[i], nums[level]); // 修改当前节点状态
backtracking(nums, level+1, ans); // 递归子节点
swap(nums[i], nums[level]); // 回改当前节点状态
}
}
};
收获
思路
和上一道例题类似,对于组合的问题,也可以进行回溯,把当前的数字加入结果中。
首先画出树形结构。可以发现如下递归结构:
递归的终止条件:当数组元素个数等于题目给出的 k 时,说明已经找到满足题目要求的数组。
否则,从 1 开始进行搜索,先把 1 填入,然后对这个子节点进行递归,下一次填入的数字将会是 2,此时数组为 [1,2],满足终止条件,退回上一步,将 2 弹出,也就是回改当前节点状态。依次类推,直到将所有节点都遍历一遍。
代码
class Solution {
public:
vector<vector<int>> combine(int n, int k) {
vector<vector<int>> ans;
vector<int> nums(k, 0);
backtracing(n, 1, k, ans, nums, 0);
return ans;
}
void backtracing(int n, int pos, int k, vector<vector<int>>& ans, vector<int>& nums, int count){
// pos 表示当前位置要放入的数字
// count 表示当前数组里的元素个数
if(count == k){
// 当数组个数已经达到 k
// 说明已经找到一个符合要求的数组
ans.push_back(nums);
return ;
}
for(int i=pos; i<=n; ++i){
nums[count++] = i; // 修改当前节点状态
backtracing(n, i+1, k, ans, nums, count); // 递归子节点
count--; // 回改当前节点状态
}
}
};
收获
思路
代码
class Solution {
public:
// vector> dir{{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
bool exist(vector<vector<char>>& board, string word) {
bool find = false;
int m = board.size(), n = board[0].size();
vector<vector<bool>> visited(m, vector<bool>(n, false));
for(int i=0; i<m; ++i){
for(int j=0; j<n; ++j){
backtracing(i, j, board, word, find, visited, 0);
}
}
return find;
}
void backtracing(int i, int j,vector<vector<char>>& board,string word,bool& find,vector<vector<bool>>& visited, int pos){
if(i<0 || i>=board.size() || j<0 || j>=board[0].size() || visited[i][j] || find || board[i][j] != word[pos])
return ;
if(pos == word.size() - 1){
find = true;
return ;
}
visited[i][j] = true;
backtracing(i+1, j, board, word, find, visited, pos+1);
backtracing(i-1, j, board, word, find, visited, pos+1);
backtracing(i, j+1, board, word, find, visited, pos+1);
backtracing(i, j-1, board, word, find, visited, pos+1);
visited[i][j] = false;
}
};
收获
题解
代码
class Solution {
public:
vector<vector<string>> solveNQueens(int n) {
vector<vector<string>> ans;
vector<string> board(n, string(n, '.'));
// 定义了三个访问数组
vector<bool> column(n, false), ldiag(2*n-1, false), rdiag(2*n-1, false);
// 按行遍历
backtracing(n, ans, board, column, ldiag, rdiag, 0);
return ans;
}
void backtracing(int n, vector<vector<string>>& ans, vector<string>& board, vector<bool>& column, vector<bool>& ldiag, vector<bool>& rdiag, int row){
// 每次遍历完一遍行 说明得到了一个答案
if(row == n){
ans.push_back(board);
return ;
}
// i 表示 column
for(int i=0; i<n ; ++i){
if(column[i] || ldiag[n-row+i-1] || rdiag[row+i])
continue;
// 修改当前状态
board[row][i] = 'Q';
column[i] = ldiag[n-row+i-1] = rdiag[row+i] = true;
// 递归子节点
backtracing(n, ans, board, column, ldiag, rdiag, row+1);
// 回改至当前的状态
board[row][i] = '.';
column[i] = ldiag[n-row+i-1] = rdiag[row+i] = false;
}
}
};
收获
vector board(n, string(n, '.'))
,感觉类似于二维的字符串数组,一个字符串里嵌套 n 个字符串。backtracing
的终止条件是: n == row
,即遍历了所有行就得到一种答案。因为是递归,最后会回到 row=0, i=0
开始第二轮的遍历(找下一个可能的结果)。回溯法辅函数 backtracing()
,通常都是返回 void ,直接调用主函数的引用对数组进行修改。核心思想由三个步骤组成:「修改当前节点状态、递归子节点、回改当前节点状态」。
分为 排列、组合 和 选择三种题型:
广度优先搜索(BFS),一层层进行遍历,因此需要使用先入先出的队列。由于是按层次进行遍历的,广度优先搜索时按照“广”的方向进行遍历,也常常用来处理最短路径问题。
思路
由于 0 代表水域,1 代表陆地,要区分两个岛屿,所以,在遍历 grid 矩阵的时候,只要第一次发现了某个格子为 1,则将发现的新大陆进行编号,即:将 1 变为 2 。
首先通过 DFS 的方式寻找第一个岛屿。
在找到第一个岛屿后,我们使用 BFS,查询其与第二个岛屿之间的距离。由于队列中保存了第一个岛屿的所有“边缘格子”,那么接下来就需要开启 while 循环,对外进行一层的岛屿扩充操作。遍历每个“边缘格子”,再分别从上/下/右/左,四个方向去查看相邻的格子。
代码
class Solution {
public:
int shortestBridge(vector<vector<int>>& grid) {
int m = grid.size(), n = grid[0].size();
vector<vector<int>> dir = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
queue<pair<int, int>> points;
bool flipped = false;
for(int i=0; i<m; ++i){
if(flipped) break;
for(int j=0; j<n; ++j){
// 使用dfs 将1均变成2
if(grid[i][j] == 1){
dfs(points, grid, i, j);
flipped = true;
break;
}
}
}
// 使用bfs寻找第二个岛屿
int x, y;
int level = 0;
while(!points.empty()){
++ level;
int n_points = points.size();
while(n_points--){
auto [r, c] = points.front();
points.pop();
for(int k=0; k<4; ++k){
x = r + dir[k][0], y = c + dir[k][1];
if(x>=0 && x<m && y>=0 && y<n){
if(grid[x][y] == 1){
return level;
}
else if(grid[x][y] == 0){
grid[x][y] = 2;
points.push({x, y});
}
else if(grid[x][y] == 2){
continue;
}
}
}
}
}
return 0;
}
void dfs(queue<pair<int, int>>& points, vector<vector<int>>& grid, int i, int j){
if(i<0 || i>=grid.size() || j<0 || j>=grid[0].size() || grid[i][j]==2)
return ;
// 如果遇到0,说明可能是路径之一 保存用于之后的遍历
if(grid[i][j] == 0){
points.push({i, j});
return ;
}
grid[i][j] = 2;
dfs(points, grid, i+1, j);
dfs(points, grid, i-1, j);
dfs(points, grid, i, j+1);
dfs(points, grid, i, j-1);
}
};
收获
queue.front()
。这道题思路也比较难,先用了 DFS 查找第一个岛屿,使用 BFS 查找第二个岛屿与第一个岛屿的最短距离。题解
代码
class Solution {
public:
vector<vector<string>> findLadders(string beginWord, string endWord, vector<string>& wordList) {
vector<vector<string>> ans;
// 因为需要快速判断扩展的单词是否出现在WordList中
// 因此使用哈希表提高效率
unordered_set<string> dict;
dict.insert(wordList.begin(), wordList.end());
// 判断 endWord 是否出现在哈希表
if(!dict.count(endWord)) return ans;
// 特殊用例处理
// 确保beginWord不在wordList - 题目要求
// 确保endWord不在WordList - 否则对于以下用例会重复输出答案
// "a" "c" ["a","b","c"]
dict.erase(beginWord);
dict.erase(endWord);
// 开始进行 BFS
// 定义两个无序集合 分别保存从beginWord/endWord出发变换的字符串列表
unordered_set<string> q1{beginWord}, q2{endWord};
//
unordered_map<string, vector<string>> next;
// reversed=false:从beginWord出发变换
// reversed=true:从endWord出发变换
bool reversed=false, found=false;
while(!q1.empty()){
// q 保存 当前的单词可以变换到的单词列表
unordered_set<string> q;
for(const auto &w : q1){
// 将当前单词保存在 w
// 对 s 的每一位替换成26位的小写字母
string s = w;
for(int i=0; i<s.size(); ++i){
// 保存要修改的字符 用于后续恢复
char ch = s[i];
for(int j=0; j<26; ++j){
s[i] = j + 'a';
if(q2.count(s)){
// 如果q2出现了s 说明此时已经变换到endWord
// reversed=true:说明从后往前,因此是将w插入到s的单词列表(反向操作)
// reversed=false:从前往后,因此是将s(变换后)插入到w(未变化)的单词列表
reversed? next[s].push_back(w) : next[w].push_back(s);
found = true;
}
if(dict.count(s)){
// 如果dict出现s 说明变换后的单词在WordList中
reversed? next[s].push_back(w) : next[w].push_back(s);
// 将其加入到q 用于后续遍历
q.insert(s);
}
}
s[i] = ch; // 恢复该字符 遍历下一种可能的情况
}
}
if(found) break;
// 要保证最短 因此单词不可以重复出现
// 删除WordList中已经出现过的单词
for(const auto &w : q){
dict.erase(w);
}
// 如果 q 的单词比 q2 少
// 那么应该遍历 q
if(q.size() <= q2.size()){
q1 = q;
}else{
// 如果 q2 的单词少 则遍历 q2
// 需要颠倒顺序
reversed = !reversed;
q1 = q2;
q2 = q;
}
}
if(found){
// 如果能够找到 那么回溯 重建这条最短路径
vector<string> path = {beginWord};
backtracking(beginWord, endWord, next, path, ans);
}
return ans;
}
void backtracking(const string& beginWord,const string& endWord, unordered_map<string, vector<string>>& next, vector<string>& path, vector<vector<string>> &ans){
if(beginWord == endWord){
ans.push_back(path);
return ;
}
for(const auto &s : next[beginWord]){
// 修改当前节点状态
path.push_back(s);
// 回溯子节点
backtracking(s, endWord, next, path, ans);
// 回改当前节点状态
path.pop_back();
}
}
};
收获
题解
代码
class Solution {
public:
void solve(vector<vector<char>>& board) {
int m = board.size(), n = board[0].size();
for(int i=0; i<m; ++i){
if(board[i][0] == 'O') dfs(board, i, 0);
if(board[i][n-1] == 'O') dfs(board, i, n-1);
}
for(int j=0; j<n; ++j){
if(board[0][j] == 'O') dfs(board, 0, j);
if(board[m-1][j] == 'O') dfs(board, m-1, j);
}
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] == '#') board[i][j] = 'O';
}
}
}
void dfs(vector<vector<char>>& board, int i, int j){
if(i<0 || i>=board.size() || j<0 || j>=board[0].size() || board[i][j]== 'X' || board[i][j] == '#') return ;
board[i][j] = '#';
// visited[i][j] = true;
dfs(board, i+1, j);
dfs(board, i-1, j);
dfs(board, i, j+1);
dfs(board, i, j-1);
// return false;
}
};
收获
board[i][j] == '#'
代替访问数组,确实可行,而且不会额外占用内存。题解
代码
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
vector<string> binaryTreePaths(TreeNode* root) {
vector<string> ans;
dfs(root, ans, "");
return ans;
}
void dfs(TreeNode* root, vector<string>& ans, string path){
if(root != nullptr){
path += to_string(root->val);
if(root ->left == nullptr && root -> right == nullptr){
ans.push_back(path);
}else{
path += "->";
dfs(root->left, ans, path);
dfs(root->right, ans, path);
}
}
}
};
收获
vector
,难怪 ans.push_back(path)
出错;思路
这道题是 46.全排列 的升级版,多了一个条件:「序列包含重复数字」。因此,相比于 46 ,可能会出现重复结果。
一开始我想用 set 去重,但是发现不是是很方便。所以,我们在遍历的时候,可以对重复结果进行剪枝。如图所示,显然,对于序列 [1,1*,2] ,如果第一个 1 已经放入在第一个位置上,那么对于 1* 来说,如果再放在第一个位置, 那么肯定和前一种情况重复,因此需要去除。
因此,如果第一个 1 已经使用,那么我们就不再考虑同一位置上的 1* 的情况。设置条件为 : if(i>0 && nums[i] == nums[i-1] && !vis[i-1]) continue;
这里,为了方便考虑重复数字,我们再遍历之前应该对数组进行排序,保证相同数字相邻。这里有一个问题,因为我代码使用了 swap 交换两个数字的位置,而每次backtracking 后,swap 都会将 nums变回 backtracking 之前的顺序,因此在回溯的时候,每次都需要对 nums 重新排序。
代码
class Solution {
public:
vector<vector<int>> permuteUnique(vector<int>& nums) {
vector<vector<int>> ans;
vector<bool> vis(nums.size(), false);
backtracking(nums, ans, 0, vis);
return ans;
}
void backtracking(vector<int>& nums, vector<vector<int>>& ans, int level, vector<bool>& vis){
if(level == nums.size()){
ans.push_back(nums);
return ;
}
for(int i=level; i<nums.size(); ++i){
// swap之后可能无序 所以需要重新排序
sort(nums.begin() + level, nums.end());
// 剪枝
if(i>0 && nums[i] == nums[i-1] && !vis[i-1]) continue;
vis[i] = true; // 修改当前状态
swap(nums[i], nums[level]);
backtracking(nums, ans, level+1, vis); // 递归子节点
vis[i] = false; // 回改当前状态
swap(nums[i], nums[level]);
}
}
};
收获
题解
代码
class Solution {
public:
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
vector<vector<int>> ans;
vector<bool> visit(candidates.size(), false);
vector<int> option;
sort(candidates.begin(), candidates.end());
backtracking(candidates, ans, target, 0, option, 0, visit);
return ans;
}
void backtracking(vector<int> &candidates, vector<vector<int>> &ans, int target, int sum, vector<int> option, int pos, vector<bool> &visit){
if(sum == target){
ans.push_back(option);
return ;
}
else if(sum > target){
return ;
}
for(int i=pos; i<candidates.size(); ++i){
// 剪枝
if(i>0 && candidates[i] == candidates[i-1] && !visit[i-1]) continue;
// 修改当前状态
visit[i] = true;
sum += candidates[i];
option.push_back(candidates[i]);
// 递归子节点
backtracking(candidates, ans, target, sum, option, i+1, visit);
// 回改当前状态
visit[i] = false;
sum -= candidates[i];
option.pop_back();
}
}
};
收获
思路
代码
class Solution {
public:
bool valid = false;
void solveSudoku(vector<vector<char>>& board) {
// 三个访问数组,分别对应于行、列、3*3
vector<vector<bool>> txt(9, vector<bool>(9, false));
vector<vector<bool>> row(9, vector<bool>(9, false));
vector<vector<bool>> col(9, vector<bool>(9, false));
// 记录需要填入的位置坐标
vector<pair<int, int>> spaces;
for(int i=0; i<9; ++i){
for(int j=0; j<9; ++j){
if(board[i][j] != '.'){
// 对已有数字进行标记 防止重复
row[i][board[i][j] - '1'] = true;
col[j][board[i][j] - '1'] = true;
txt[(i / 3) * 3 + (j / 3)][board[i][j] - '1'] = true;
}
else{
// 记录需要填入的位置坐标
spaces.push_back({i, j});
}
}
}
backtracking(board, 0, row, col, txt, spaces);
}
void backtracking(vector<vector<char>>& board, int pos, vector<vector<bool>> &row, vector<vector<bool>> &col, vector<vector<bool>> &txt, vector<pair<int, int>> &spaces){
// pos 表示当前遍历到 spaces的元素下标
// 递归结束的条件
if(pos == spaces.size()){
// 说明每个需要填入的位置都已经遍历了
valid = true;
return ;
}
auto [i, j] = spaces[pos];
for(int k=0; k<9; ++k){
if(!valid && !row[i][k] && !col[j][k] && !txt[(i / 3) * 3 + (j / 3)][k]){
// 说明这个数字还没被使用
// 修改当前节点的状态
board[i][j] = k + '0' + 1;
row[i][k] = col[j][k] = txt[(i / 3) * 3 + (j / 3)][k] = true;
// 递归子节点
backtracking(board, pos+1, row, col, txt, spaces);
// 不需要再改回"."
// 回改当前状态
row[i][k] = col[j][k] = txt[(i / 3) * 3 + (j / 3)][k] = false;
}
}
}
};
收获
思路
代码
class Solution {
public:
vector<int> findMinHeightTrees(int n, vector<vector<int>>& edges) {
if(n==1) return {0};
vector<vector<int>> m(n); // 邻接表
vector<int> degree(n, 0);
vector<int> ans;
// 保存邻接矩阵和度
for(int i=0; i<edges.size(); ++i){
int u = edges[i][0], v = edges[i][1];
++degree[u];++degree[v];
m[u].push_back(v);
m[v].push_back(u);
}
queue<int> q;
// 叶子节点入队
for(int i=0; i<n; ++i){
if(degree[i] == 1) q.push(i);
}
// 一层层从外往内剥,剩下的叶子节点就是根节点
while(!q.empty()){
// ans 会保存最后状态下的叶子节点
ans.clear();
int q_size = q.size();
for(int i=0; i<q_size; ++i){
int t = q.front();
q.pop();
ans.push_back(t);
for(auto j : m[t]){
// 由于剥掉了外层 因此它的度-1
degree[j] --;
// 如果当前节点是叶子节点 入队
if(degree[j] == 1) q.push(j);
}
}
}
return ans;
}
};
收获