赞
踩
广度优先搜索(BFS)一般由队列实现,其模板为:
- void BFS(int s){
- queue<int> q;
- q.push(s);
- while(!q.empty()){
- 取队首元素top;
- 访问队首元素top;
- 队首元素出队pop;
- for(下一层未入队结点){
- 入队,并设置为已入队
- }
- }
- }
深度优先搜索(DFS)可以用递归(调用系统栈实现)或栈实现。
栈模板
- void DFS(int s){
- stack<int> st;
- st.push(s);
- while(!st.empty()){
- 取栈顶元素top;
- 访问队顶元素top;
- 栈顶元素出栈pop;
- for(下一层未入栈结点){
- 入队,并设置为已入栈
- }
- }
- }
递归模板
- void DFS(){
- if(抵达边界条件) return;
- 修改某些边界条件或标志量;
- //岔道口,剪枝减少计算量
- if(满足某条件){//递归进入分支
- DFS();
- }
- }
1、岛屿的最大面积(695)
搜索问题经典模板题,先介绍BFS思路:遍历二维数组每一个位置,如果是0跳过,如果为1,则判断其相邻4个位置是否为1(前提不能出界)。为防止重复访问,增加辅助数组标记,如果入过队则标记已访问。(当然本题可以直接把访问过的结点置为0更简单)直到所有数组被访问。
- class Solution {
- public:
- //辅助方向数组,用于移动访问上下左右4个方向
- vector<int> directions{-1,0,1,0,-1};
-
- int BFS(vector<vector<int>>& grid,int x,int y){
- if(grid[x][y]==0) return 0;
- queue<pair<int,int>> q;
- q.push({x,y});
- grid[x][y] = 0;//置为0表示已访问过
- int area = 0;
- while(!q.empty()){
- pair<int,int> top = q.front();
- q.pop();
- ++area;
- for(int i=0;i<4;i++){
- int newX = top.first + directions[i];
- int newY = top.second + directions[i+1];
- if(newX>=0 && newX<grid.size() && newY>=0 && newY<grid[newX].size() && grid[newX][newY]==1){
- grid[newX][newY] = 0;
-
- q.push({newX,newY});
- }
- }
- }
- return area;
- }
-
- int maxAreaOfIsland(vector<vector<int>>& grid) {
- int maxArea = 0;
- for(int i=0; i<grid.size();i++){
- for(int j=0; j<grid[i].size(); j++){
- maxArea = max(maxArea,BFS(grid,i,j));
- }
- }
- return maxArea;
-
- }
- };
pair和队列混合使用不太习惯,其实可以用两个queue分别存储x和y坐标。
DFS思路,可以用栈也可以用递归方式,递归更易于实现,但一般为了防止出现递归栈满的情况,用栈比较合适。
- class Solution {
- public:
-
- int maxAreaOfIsland(vector<vector<int>>& grid) {
- vector<int> directions{-1,0,1,0,-1};
- int maxArea = 0, m = grid.size(), n = grid[0].size();
- stack<pair<int,int>> st;
- for(int x=0; x<m; x++){
- for(int y=0; y<n; y++){
- int area = 0;
- if(grid[x][y]==1){
- grid[x][y] = 0;
- st.push({x,y});
- while(!st.empty()){
- auto [r,c] = st.top();
- st.pop();
- ++area;
- for(int i=0;i<4;i++){
- int newx = r + directions[i];
- int newy = c + directions[i+1];
- if(newx>=0 && newx<m && newy>=0 && newy<n && grid[newx][newy]==1 ){
- st.push({newx,newy});
- grid[newx][newy] = 0;
- }
- }
- }
- }
- maxArea = max(maxArea,area);
- }
- }
- return maxArea;
- }
- };
语法:stack或queue中加入pair元素,使用.push({x,y}),读取auto[x,y] = .top()
递归方式
- class Solution {
- public:
- vector<int> directions{-1,0,1,0,-1};
- int DFS(vector<vector<int>>& grid,int r,int c){
- //边界条件
- if(grid[r][c]==0) return 0;
- grid[r][c] = 0;
- int area = 1;
- for(int i=0; i<4; i++){
- int x = r + directions[i], y = c + directions[i+1];
- if(x>=0 && x<grid.size() && y>=0 && y<grid[0].size()){
- area += DFS(grid,x,y);
- }
- }
- return area;
- }
- int maxAreaOfIsland(vector<vector<int>>& grid) {
- if(grid.empty() || grid[0].empty()) return 0;
- int maxArea = 0;
- for(int i=0;i<grid.size();i++){
- for(int j=0;j<grid[0].size();j++){
- maxArea = max(maxArea,DFS(grid,i,j));
- }
- }
- return maxArea;
- }
- };
2、省份数量(547)
本质上是求连通子图个数。
思路一:DFS\BFS。以DFS为例,对城市 i ,考察还未访问的城市 j 与之是否同省(对应值为1),若同省,则递归对j进行上述过程,并标记 j 被访问。本质上就是一次调用DFS函数把某一个省的城市都找出来并标记,则调用DFS次数就是省份数量。
递归写法
- class Solution {
- public:
-
- void DFS(vector<vector<int>>& isConnected,int i,vector<bool>& isFind){
- isFind[i] = true;
- for(int j = 0; j<isConnected.size(); j++){
- if(isConnected[i][j]==1 && !isFind[j]){
- DFS(isConnected,j,isFind);
- }
- }
- }
- int findCircleNum(vector<vector<int>>& isConnected) {
- int n = isConnected.size(),numProvices = 0;
- //vector初始化方法为同一值
- vector<bool> isFind(n,false);
- for(int i=0; i<n; i++){
- if(!isFind[i]){
- DFS(isConnected,i,isFind);
- ++numProvices;
- }
- }
- return numProvices;
- }
- };
栈实现方法
- class Solution {
- public:
- int findCircleNum(vector<vector<int>>& isConnected) {
- int n = isConnected.size(),numProvices = 0;
- //vector初始化方法为同一值
- vector<bool> visited(n,false);
- for(int i=0; i<n; i++){
- if(!visited[i]){
- stack<int> st;
- st.push(i);
- visited[i] =true;
- while(!st.empty()){
- int first = st.top();
- st.pop();
- for(int j=0; j<n; j++){
- if(isConnected[first][j]==1 && !visited[j]){
- st.push(j);
- visited[j] = true;
- }
- }
- }
- ++numProvices;
- }
-
- }
- return numProvices;
- }
- };
方法二:使用并查集
并查集的核心思想是:用数组father[ i ]存储数组 i 的父节点,从而实现合并(集合的合并)和查找(判断两个元素是否在一个集合)的操作。我们可以进行路径压缩,使同一个集合元素共有一个父节点。
集合数量统计可以开辟一个bool数组,记录是否为根节点。当然由于进行了路径压缩,也可以直接看father[i] == i的数量。
- class Solution {
- public:
- //查找x所在集合的根节点
- int findFather(vector<int>& father,int x){
- int a = x;
- //找到父节点
- while(x!=father[x]){
- x = father[x];
- }
- //路径压缩
- while(a!=father[a]){
- int z = a;
- a = father[a];
- father[z] = x;
- }
- return x;
- }
-
-
- int findCircleNum(vector<vector<int>>& isConnected) {
- int n = isConnected.size(),numProvices = 0;
- vector<int> father(n);// 存放父节点
- vector<int> isRoot(n,false);//记录每个结点是否作为根节点,便于统计省份数目
- // 父节点初始化
- for(int i=0; i<n; i++){
- father[i] = i;
- }
-
- for(int i=0; i<n; i++){
- for(int j=0; j<i; j++){//合并同省份城市所在集合
- if(isConnected[i][j]==1){
- int faI = findFather(father,i);
- int faJ = findFather(father,j);
- if(faI != faJ){
- father[faI] = faJ;
- }
- }
- }
- }
-
- //i的跟结点为findFather[i]
- for(int i=0;i<n;i++){
- isRoot[findFather(father,i)] = true;
- }
-
- for(int i=0;i<n;i++){
- numProvices += isRoot[i];
- }
-
- return numProvices;
- }
- };
语法:初始化vector数组为同一数组,vector<int> isRoot(n,false);
3、太平洋大西洋水流问题(417)
如果对每个位置进行搜索判断,不剪枝情况下时间复杂度非常高,因此反向思考,从4个边界向中间高处流,可以被抵达的点就是答案,这样只需要考察4个边界。
- class Solution {
- public:
- vector<int> directions{-1,0,1,0,-1};
- //判断(r,c)向高处流能到达哪些点
- void dfs(vector<vector<int>>& heights,vector<vector<bool>>& can_reach,int r,int c){
- if(can_reach[r][c]) return;
- can_reach[r][c] = true;
- for(int i=0; i<4; i++){
- int x = r + directions[i], y = c + directions[i+1];
- if(x>=0 && x<heights.size() && y>=0 && y<heights[0].size() && heights[r][c]<=heights[x][y]){
- dfs(heights,can_reach,x,y);
-
- }
- }
- }
- vector<vector<int>> pacificAtlantic(vector<vector<int>>& heights) {
- if(heights.size()==0 || heights[0].size()==0) return {};
- 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));
- for(int i=0; i<m; i++){
- //考察哪些点能抵达左右边界
- dfs(heights,can_reach_p,i,0);
- dfs(heights,can_reach_a,i,n-1);
- }
- for(int i=0; i<n; i++){
- //考察哪些点能抵达上下边界
- dfs(heights,can_reach_p,0,i);
- dfs(heights,can_reach_a,m-1,i);
- }
-
- vector<vector<int>> ans;
- for(int i=0; i<m; i++){
- for(int j=0; j<n; j++){
- if(can_reach_p[i][j] && can_reach_a[i][j]){
- ans.push_back(vector<int>{i,j});
- }
- }
- }
- return ans;
- }
- };
语法:初始化二维vector,如vector<vector<bool>> can_reach_p (m,vector<bool>(n,false));
4、全排列(46)
全排列过程可以这样递归实现。递归过程:让n个元素轮流作为首位,对剩余n-1个元素进行全排列,我们可以用交换的方法,把每个元素交换到首位。递归边界:当只有1个元素,全排列即为本身。
对n-1个结点完成全排列后,我们需要恢复之前的状态,把后面的元素交换到首位,这就涉及到回溯法。回溯法比起DFS多了一个递归后修改回原结点状态的过程(使用引用传递状态)。
- class Solution {
- public:
- void backtracking(vector<int>& nums,int level,vector<vector<int>>& ans){
- //边界,交换到最后一个元素
- if(level == nums.size()-1){
- ans.push_back(nums);
- return;
- }
- //level位元素和之后所有元素逐一交换,然后递归,之后回溯法修改回原状态
- for(int i=level; i<nums.size(); i++){
- swap(nums[i],nums[level]);
- backtracking(nums,level+1,ans);
- swap(nums[i],nums[level]);//回溯,修改回原状态
- }
- }
- vector<vector<int>> permute(vector<int>& nums) {
- vector<vector<int>> ans;
- backtracking(nums,0,ans);
- return ans;
- }
- };
5、组合(77)
递归链条:从区间[st, n]中选出k个整数,如果选中st,则在[st+1, n]中选择k-1个整数;没选中st,则在[st+1, n]中选择k个整数。
递归边界:如果n-st+1<k,无法实现组合;如果k=0,已经找到k个数组合了。
回溯法运用:用nums数组存储已经选中的数字,用pop_back维持未选中的状态。
- class Solution {
- public:
- //参数k表示还需选中的数字个数
- void backtracking(int st, int n, int k,vector<int>& nums,vector<vector<int>>& ans){
- if(n-st+1 < k) return;//剩余元素不足k个,无法实现组合
- if(k == 0) {//已经本组组合找齐元素了
- ans.push_back(nums);
- return;
- }
-
- //选中st
- nums.push_back(st);
- backtracking(st+1,n,k-1,nums,ans);
- //没选中st
- nums.pop_back();
- backtracking(st+1,n,k,nums,ans);
-
- }
- vector<vector<int>> combine(int n, int k) {
- vector<int> nums;
- vector<vector<int>> ans;
- backtracking(1,n,k,nums,ans);
- return ans;
- }
- };
6、单词搜索(79)
递归过程:如果当前字符board[r][c]与目标单词的字母word[idx]相同,则对其上下左右(不出界)且没有被访问的结点,进行递归判断,看其是否是word[idx+1]。
递归边界:当idx == word.size()-1,且当前字符board[r][c]与目标单词的字母word[idx]相同,则找到了该单词。
回溯:如果子递归找不到该单词,重新设置board[r][c]为未访问。
- class Solution {
- public:
- vector<int> directions{-1,0,1,0,-1};
- void backtracking(vector<vector<char>>& board,string word,vector<vector<bool>>& visited,int r, int c, bool& flag,int idx){
-
- if(visited[r][c]==false && board[r][c] == word[idx]){
- if(idx = word.length()-1){
- flag = true;
- return;
- }
- visited[r][c] = true;//(r,c)已被选用
- for(int i=0;i<4;i++){
- int x = r + directions[i], y = c + directions[i+1];
- if(x>=0 && x<board.size() && y>=0 && y<board[0].size()){
- backtracking(board,word,visited,x,y,flag,idx+1);
- }
- }
- visited[r][c] = false;
-
- }
- }
- bool exist(vector<vector<char>>& board, string word) {
- int m = board.size(), n = board[0].size();
- bool flag = false;
- vector<vector<bool>> visited(m,vector<bool>(n,false));
- for(int i=0;i<m;i++){
- for(int j=0;j<n;j++){
- backtracking(board,word,visited,i,j,flag,0);
- if(flag) return true;
- }
- }
- return false;
- }
- };
7、N 皇后(51)
经典N皇后问题,同样采用回溯法。
递归过程:N皇后排列结果必定是每行和每列仅有1个皇后,因此我们可以按行填入。当我们在第row行第 i 个位置放入一个皇后时,则第i个位置对应的竖行、左、右斜行都不可再填入(用对应数组标识),然后对第row+1行递归,寻找合适位置填入下一个皇后。
递归边界:当放置第N行时(编号0~N-1),已经找到一种摆法,保存结果。
回溯:递归后,把第row行第 i 个位置摆放的皇后,第i个位置对应竖行、左、右斜行修改回来。
N皇后的问题的难点主要在于选中一个位置放入皇后,要如何把其对应的行,列,左右斜行给修改,使之不能再填入别的皇后。特别是要注意到同一斜行与其行、列号的关系。
- class Solution {
- public:
- void backtracking(vector<vector<string>>& ans,vector<string>& board, vector<bool>& columns, vector<bool>& ldiag, vector<bool>& rdiag,int row,int n){
- //已经摆放了n个皇后了
- if(row == n){
- ans.push_back(board);
- return;
- }
- //在第row行寻找合适位置放入皇后
- for(int i=0; i<n; i++){
- //判断第i个位置对应的列、左、右斜行是否已有皇后
- //以左上为原点,从左上到右下给左斜行编号[0~2n-1],其左斜行号 = 行号 + 列号
- //从右上到左下给右斜行编号[0~2n-1],其右斜行号 = 行号 - 列号 + (n-1)
- if(columns[i] || ldiag[row+i] || rdiag[row - i + n -1]){
- continue;
- }
- //在找到的合适位置放入皇后,并修改对应列、左、右斜行
- board[row][i] = 'Q';
- columns[i] = ldiag[row+i] = rdiag[row - i + n -1] = true;
- //对第row+1行递归
- backtracking(ans,board,columns,ldiag,rdiag,row+1,n);
- //回溯
- board[row][i] = '.';
- columns[i] = ldiag[row+i] = rdiag[row - i + n -1] = false;
- }
- }
- vector<vector<string>> solveNQueens(int n) {
- vector<vector<string>> ans;
- if(n==0) return ans;
- //存储摆放结果
- vector<string> board(n,string(n,'.'));
- //ldiag和rdiag为左右斜行,n*n方格有2n-1个左、右斜行
- vector<bool> columns(n,false), ldiag(2*n-1,false),rdiag(2*n-1,false);
- backtracking(ans,board,columns,ldiag,rdiag,0,n);
- return ans;
- }
- };
8、最短的桥(934)
本题首先要认识到,从一个岛到另一岛的最短距离 = 从一个岛进行宽度优先搜索找到另一个岛扩展层数 -1。具体思路:
(1)先用深度优先搜索,把第一个岛的结点全部找出加入队列;
(2)bfs向外拓展,直到找到另一个岛,所有入队的结点把值改为2,标记为已访问。
- class Solution {
- public:
- queue<pair<int,int>> q;
- vector<int> directions{-1,0,1,0,-1};
- void dfs(vector<vector<int>>& grid,int r,int c){
- grid[r][c] = 2;
- q.push({r,c});
- for(int i=0; i<4; i++){
- int x = r + directions[i], y = c + directions[i+1];
- if(x>=0 && x<grid.size() && y>=0 && y<grid.size() && grid[x][y]==1){
- dfs(grid,x,y);
- }
- }
- }
- int shortestBridge(vector<vector<int>>& grid) {
- int n = grid.size();
- //先用dfs找出一个岛的所有结点入队,并把1改为2用于标识已访问
- int flag = false;
- for(int i=0; i<n; i++){
- for(int j=0; j<n; j++){
- if(grid[i][j]==1){
- dfs(grid,i,j);
- flag = true;
- break;
- }
- }
- if(flag) break;
- }
-
- //用bfs向外搜索,到碰见1时向外搜索的层数-1即为桥数
- int layer = 0;
- while(!q.empty()){
- ++layer;
- //把本层全部取出
- int num = q.size();
- while(num--){
- auto [r,c] = q.front();
- q.pop();
- for(int k=0; k<4; k++){
- int x = r + directions[k], y = c + directions[k+1];
- if(x>=0 && x<n && y>=0 && y<n){
- if(grid[x][y]==1) return layer-1;//拓展到另一个岛
- else if(grid[x][y]==2) continue;//已入队结点
- else if(grid[x][y]==0) {//0结点入队,并修改为2标记已入过队
- grid[x][y] = 2;
- q.push({x,y});
- }
- }
- }
- }
- }
- return 0;
- }
- };
9、被围绕的区域(130)
本题类似于前面的找岛问题,不同在于不考虑有边界点的岛。我的思路是:用数组存储岛的结点,再用一个标志变量flag存储该岛是否存在边界结点,如果不存在,则把数组中的结点修改为X。
- class Solution {
- public:
- vector<int> direction{-1,0,1,0,-1};
- void dfs(vector<vector<char>>& board, vector<vector<bool>>& visited, vector<pair<int,int>>& pos, int r,int c,bool& flag){
- visited[r][c] = true;
- if(r==0 || r==board.size()-1 || c==0 || c==board[0].size()-1){//边界上的结点
- flag = false;
-
- }
- pos.push_back({r,c});
- for(int i=0;i<4;i++){
- int x = r + direction[i], y = c + direction[i+1];
- if(x>=0 && x<board.size() && y>=0 && y<board[0].size() && board[x][y]=='O' && !visited[x][y]){
- dfs(board,visited,pos,x,y,flag);
- }
- }
- }
-
- void solve(vector<vector<char>>& board) {
-
- 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++){
- if(board[i][j]=='O' && !visited[i][j]){
- bool flag = true;//判断岛是否有边界结点
- vector<pair<int,int>> pos;//存储岛的结点
- dfs(board,visited,pos,i,j,flag);
- if(flag){
- for(auto[x,y]: pos){
- board[x][y] = 'X';
- }
- }
-
- }
- }
- }
- }
- };
参考题解发现更优思路:可以先对边界点进行dfs,把包含边界点的岛修改为‘A’。之后对所有结点遍历,若结点为'A',则改回‘O’,若结点为'O'则修改为'X'。
- class Solution {
- public:
- int n, m;
-
- void dfs(vector<vector<char>>& board, int x, int y) {
- if (x < 0 || x >= n || y < 0 || y >= m || board[x][y] != 'O') {
- return;
- }
- board[x][y] = 'A';
- dfs(board, x + 1, y);
- dfs(board, x - 1, y);
- dfs(board, x, y + 1);
- dfs(board, x, y - 1);
- }
-
- void solve(vector<vector<char>>& board) {
- n = board.size();
- if (n == 0) {
- return;
- }
- m = board[0].size();
- for (int i = 0; i < n; i++) {
- dfs(board, i, 0);
- dfs(board, i, m - 1);
- }
- for (int i = 1; i < m - 1; i++) {
- dfs(board, 0, i);
- dfs(board, n - 1, i);
- }
- for (int i = 0; i < n; i++) {
- for (int j = 0; j < m; j++) {
- if (board[i][j] == 'A') {
- board[i][j] = 'O';
- } else if (board[i][j] == 'O') {
- board[i][j] = 'X';
- }
- }
- }
- }
- };
-
10、二叉树的所有路径(257)
采用回溯法。
递归过程:一个结点左子树不为空时,把左子树值加入str,向左子树递归,递归完回溯,把插入值去掉,恢复str;右子树同理。
递归边界,一个结点左右子树都为空时,到达了叶子结点,把str加入ans,返回。
- class Solution {
- public:
- void dfs(vector<string>& ans, string& str,TreeNode* root){
- if(root->left == nullptr && root->right == nullptr){//抵达叶子结点
- ans.push_back(str);
- return;
- }
- if(root->left != nullptr){
- int pos = str.size();
- str += "->";
- str += to_string(root->left->val);//to_stirng函数把int转化为string
- dfs(ans,str,root->left);
- //回溯,把插入的左子树部分删除
- str.erase(str.begin()+pos,str.end());
- }
- if(root->right != nullptr){
- int pos = str.size();
- str += "->";
- str += to_string(root->right->val);
- dfs(ans,str,root->right);
- str.erase(str.begin()+pos,str.end());
- }
-
-
- }
- vector<string> binaryTreePaths(TreeNode* root) {
- if(root == nullptr) return {};
- //因为根节点前面没有->,所以把把根节点值先加入
- string str = to_string(root->val);
- vector<string> ans;
- dfs(ans,str,root);
- return ans;
- }
- };
语法:to_string()把数值型数据(如int\double\long等)转化为string。
在仔细思考,其实这题中存储某条路径的str不需要引用传递,直接用形参就复制了一份副本,不需要删除回溯这个操作了。
- class Solution {
- public:
- void construct_paths(TreeNode* root, string path, vector<string>& paths) {
- if (root != nullptr) {
- path += to_string(root->val);
- if (root->left == nullptr && root->right == nullptr) { // 当前节点是叶子节点
- paths.push_back(path); // 把路径加入到答案中
- } else {
- path += "->"; // 当前节点不是叶子节点,继续递归遍历
- construct_paths(root->left, path, paths);
- construct_paths(root->right, path, paths);
- }
- }
- }
-
- vector<string> binaryTreePaths(TreeNode* root) {
- vector<string> paths;
- construct_paths(root, "", paths);
- return paths;
- }
- };
11、全排列 II(47)
本题是全排列的变形题,区别在于本题存在重复的元素,在回溯法的过程中会产生重复结果。这些重复结果产生的原因是,重复元素背交换到pos位,因此可以建立一个集合,存储pos中出现过的元素,只有没出现在pos位上的元素才能执行后续操作和递归。
- class Solution {
- public:
- void backtracking(vector<int>& nums,vector<vector<int>>& ans,int pos){
- if(pos == nums.size()-1){
- ans.push_back(nums);
- return;
- }
- unordered_set<int> s;
- for(int i=pos; i<nums.size(); i++){
- if(!s.count(nums[i])){
- s.insert(nums[i]);
- swap(nums[i],nums[pos]);
- backtracking(nums,ans,pos+1);
- swap(nums[i],nums[pos]);
- }
- }
- }
- vector<vector<int>> permuteUnique(vector<int>& nums) {
- vector<vector<int>> ans;
- backtracking(nums,ans,0);
- return ans;
- }
- };
12、组合总和(39)
组合数的变形题,要求从组合出k个数变成了组合数字的和为target。仍然可以用回溯法,设candidates共有n个数。
递归过程:若选中第k个数时,则对[k,n-1]继续递归,使[k,n-1]中选中的数字和等于sum - candidates[k];若不选中第k个数时,回溯,去掉选中candidates[k],的则对[k+1,n-1]继续递归,使[k+1,n-1]中选中的数字和等于sum 。
递归边界:若当前sum=0,则保存答案返回;若sum<0或是k==n,则直接返回。
- class Solution {
- public:
- void backtracking(vector<vector<int>>& ans,vector<int>& candidates,vector<int>& nums,int sum,int k){
- if(sum == 0){//这一边界条件要放在上面,不然可能会在sum=0,而candidates.size()时直接返回
- ans.push_back(nums);
- return;
- }
- if(sum<0 || k==candidates.size()){
- return;
- }
- //选中candidates[k]
- nums.push_back(candidates[k]);
- backtracking(ans,candidates,nums,sum - candidates[k],k);
- //未选中candidates[k]
- nums.pop_back();
- backtracking(ans,candidates,nums,sum,k+1);
- }
- vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
- vector<vector<int>> ans;
- vector<int> nums;
- backtracking(ans,candidates,nums,target,0);
- return ans;
- }
- };
13、组合总和 II(40)
变式题,这一题里元素本身就包含重复元素,每个元素只能用一次。难点在于如何产生不重复的结果。如下例:
输入: candidates = [10,1,2,7,6,1,5], target = 8,
输出:
[
[1,1,6],
[1,2,5],
[1,7],
[2,6]
]
candiates中有两个1,但是只能产生一个[1,7]的结果,因此需要改进递归条件。为了不让重复的元素执行相同的递归过程,得到相同的递归结果,对相同元素,只取一个进行递归即可。为此,我们先把candiates进行排序,相同元素会连续排列,选中第一个进行递归,不选中时跳到下一个不同元素进行递归。
- class Solution {
- public:
- void backtracking(vector<vector<int>>& ans,vector<int>& candidates,vector<int>& nums,int sum,int k){
- if(sum == 0){//这一边界条件要放在上面,不然可能会在sum=0,而candidates.size()时直接返回
- ans.push_back(nums);
- return;
- }
- if(sum<0 || k==candidates.size()){
- return;
- }
- //选中candidates[k]
- nums.push_back(candidates[k]);
- backtracking(ans,candidates,nums,sum - candidates[k],k+1);
- //未选中candidates[k],为了不产生重复结果,跳到下一个不同的元素递归
- nums.pop_back();
- int i=k+1;
- for(i=k+1;i<candidates.size();i++){
- if(candidates[i]!=candidates[k]) break;
- }
- backtracking(ans,candidates,nums,sum,i);
- }
- vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
- sort(candidates.begin(),candidates.end());
- vector<vector<int>> ans;
- vector<int> nums;
- backtracking(ans,candidates,nums,target,0);
- return ans;
- }
- };
14、解数独(37)
这题看成是N皇后问题的扩展题,都是不同方格间填入元素的存在制约关系,需要利用这一关系剪枝,搜索出最终结果。N皇后是行、列、斜行间的制约关系,数独是行、列、九宫格内的制约关系。所以思路也类似。维系row, col, block的辅助数组,记录(r, c)点所在行、列、块内已经使用过的数字。
递归思路:如果(r, c)点是'.’,则根据对应row, col, block,选出所有可能值填入,并修改row, col, block辅助状态,之后移动到下一个点递归,然后回溯,把填入值、辅助数组值改回来,;如果(r, c)点已经有数字,直接递归下一个点。
递归边界:按行优先移动点,当递归到(9,0)点时,说明已经得到答案,保存结果后返回。
- class Solution {
- public:
- //记录每一行、列、九宫格已经使用的数字
- bool row[9][10] = {false}, col[9][10] = {false}, block[3][3][10] = {false};
- char ans[9][9];//记录最终答案
- void backtracking(vector<vector<char>>& board,int r,int c){
- if(r==9 && c==0){
- for(int i=0;i<9;i++){
- for(int j=0;j<9;j++){
- ans[i][j] = board[i][j];
- }
- }
- return;
- }
- int x,y;//下一个递归点
- if(c==8) {
- x = r+1;
- y = 0;
- }else{
- x = r;
- y = c+1;
- }
- if(board[r][c]=='.'){
- //寻找1-9间的合适值填入
- for(int i=1;i<10;i++){
- if(row[r][i] || col[c][i] || block[r/3][c/3][i]){
- //(r,c)所在行、列或九宫格已经有数字i
- continue;
- }
- row[r][i] = col[c][i] = block[r/3][c/3][i] = true;
- board[r][c] = '0' + i;
-
- backtracking(board,x,y);
- //回溯
- row[r][i] = col[c][i] = block[r/3][c/3][i] = false;
- board[r][c] = '.';
- }
- }else{
- backtracking(board,x,y);
- }
-
- }
-
- void solveSudoku(vector<vector<char>>& board) {
- //更新记录表
- for(int i=0;i<9;i++){
- for(int j=0;j<9;j++){
- if(board[i][j]!='.'){
- int num = board[i][j] - '0';
- row[i][num] = true;
- col[j][num] = true;
- block[i/3][j/3][num] = true;
- }
- }
- }
-
- backtracking(board,0,0);
- for(int i=0;i<9;i++){
- for(int j=0;j<9;j++){
- board[i][j] = ans[i][j];
- }
- }
-
- }
- };
15、最小高度树(310)
本题最直接的思路是,把边读入,按邻接表存储图。以每个结点为根结点,进行dfs或bfs,求出当前树的高度,并更新最小树高,保存结果。但这样的方法时间复杂度较高,在结点很多时会超过时间界限。
- class Solution {
- public:
-
- vector<int> findMinHeightTrees(int n, vector<vector<int>>& edges) {
- vector<vector<int>> G(n);
- for(int i=0;i<n-1;i++){
- int a = edges[i][0], b = edges[i][1];
- G[a].push_back(b);
- G[b].push_back(a);
- }
-
- vector<int> ans;
- int min_depth = n;
- for(int i=0; i<n; i++){
- int depth = -1;
- vector<bool> visited(n,false);
- queue<int> q;
- q.push(i);
- visited[i] = true;
- while(!q.empty()){
- int size = q.size();
- ++depth;
- for(int j=0; j<size;j++){
- int root = q.front();
- q.pop();
- for(int k=0; k<G[root].size(); k++){
- int node = G[root][k];
- if(!visited[node]){
- q.push(node);
- visited[node] = true;
- }
- }
- }
- }
-
- if(min_depth>depth){
- min_depth = depth;
- ans.clear();
- ans.push_back(i);
- }else if(min_depth == depth){
- ans.push_back(i);
- }
- }
- return ans;
- }
- };
参考大佬解答,注意发现以图中心的结点作为根,树高会更小,而用边缘结点(如叶子)树高会较大。如下图示例:0、2、3都是边缘叶子结点,所以树高都较大,而中心的1作为根,则树高较小。
既然度为1的边缘结点会是不可能作为最小树高结点,那么我们可以逐步剥去剩余图中度为1的边缘结点,在剥完所有结点时,最后被剥去的那层结点就是答案。
- class Solution {
- public:
-
- vector<int> findMinHeightTrees(int n, vector<vector<int>>& edges) {
- if(n==1) return {0};
- vector<vector<int>> G(n);
- for(int i=0;i<n-1;i++){//邻接表存储图
- int a = edges[i][0], b = edges[i][1];
- G[a].push_back(b);
- G[b].push_back(a);
- }
-
- vector<int> ans,degree(n);
- queue<int> q;
- //统计结点的度,并把度为1的结点存进队列
- for(int i=0;i<n;i++){
- degree[i] = G[i].size();
- if(G[i].size()==1) {
- q.push(i);
- }
- }
-
- //向内层结点进行bfs搜索
- while(!q.empty()){
- //还有内层结点,把存储的上一层结点清空
- ans.clear();
-
- int size = q.size();
- for(int i=0; i<size; i++){
- int root = q.front();
- ans.push_back(root);//存储本层结点
- --degree[root];//该结点被剥去,度减少为0,避免重复入队
- q.pop();
- //bfs搜索,把剥去外层结点后度变为1的结点加入队列
- for(int j=0; j<G[root].size(); j++){
- int node = G[root][j];
- --degree[node];
- if(degree[node]==1){
- q.push(node);
- }
- }
- }
- }
- return ans;
- }
- };
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。