LeetCode DFS搜索与回溯专题

DFS + 回溯专题

17. 电话号码的字母组合

迭代也可以实现搜索

循环改写dfs搜索的写法:

例如

C++写法

class Solution {
public:
    vector<string> letterCombinations(string digits) {
        string alp[8] = {"abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
        vector<string> state;
        if(digits.empty()) return state;
        state.push_back("");
        for(int i=0;i<digits.size();i++){
            char number = digits[i];
            vector<string> now;
            for(int j=0;j<alp[number-'2'].size();j++){
                char ch = alp[number-'2'][j];
                for(int k = 0;k < state.size();k++){
                    now.push_back(state[k] + ch);
                }
            }
            state = now;
        }
        return state;
    }
};

C++11+ forEach遍历,新语法写法

class Solution {
public:
    vector<string> letterCombinations(string digits) {
        string alp[8] = {"abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
        vector<string> state;
        if(digits.empty()) return state;
        state.push_back("");
        for(auto digit : digits){ //对digits进行遍历 取得每一个按键数字
            vector<string> now;
            for(auto c : alp[digit - '2']){ //对数字对应的string能表示的形式遍历
                for(auto s : state){ //对state集合里的状态进行遍历
                    now.push_back(s + c);
                }
            }
            state = now;
        }
        return state;
    }
};

两种写法耗时对比:

79. 单词搜索

搜索题

考虑顺序:

1.枚举起点 n×m

2.从起点开始依次搜索下一个点的位置 上下左右走3种

3.在枚举的过程中要保证和目标单词匹配

时间复杂度 n×m × 3^k,k为单词字符串word的长度

class Solution {
public:
    int n,m;
    int dx[4] = {1,-1,0,0},dy[4] = {0,0,-1,1}; //上下左右4个方向
    bool exist(vector<vector<char>>& board, string word) {
        if(board.empty()  || board[0].empty())  return false;
        n = board.size(),m = board[0].size();
        for(int i=0;i<n;i++){ //1.枚举起点(第一个字符向匹配的点)
            for(int j=0;j<m;j++){
                if(dfs(board,i,j,word,0)) return true; //2.从起点开始搜索
            }
        }
        return false;
    }

    bool dfs(vector<vector<char>>& board,int x,int y,string word,int k){
        if(board[x][y] != word[k]) return false;
        //递归出口 当最后一个字符到达
        if(k == word.size() - 1) return true; 
        board[x][y] = '.'; //标记已访问过 用过
        for(int i=0;i<4;i++){ //枚举4个方向
            int nx = x + dx[i],ny = y + dy[i];
            if(nx >= 0 && nx < n && ny >=0 && ny < m){
            	//递归搜索单词的下一个字符
                if(dfs(board,nx,ny,word,k+1)){ 
                    return true;
                }
            }
        }
        board[x][y] = word[k]; //回溯
        return false;
    }
};

46. 全排列

全排列,先考虑顺序问题,不能重复

全排列有两种方案

第一种方案,枚举每个位置上放哪个数字,每次dfs搜索时枚举这个数标记、回溯

class Solution {
public:
    int n;
    vector<bool> vis;
    vector<int> path;
    vector<vector<int> > result;

    vector<vector<int>> permute(vector<int>& nums) {
        n = nums.size();
        vis = vector<bool>(n,false); //初始化bool容器
        dfs(nums,0);
        return result;
    }
	
	//枚举第k个数该放哪个数字 即枚举数字
    void dfs(vector<int>& nums,int k){
        if(k == n){
            result.push_back(path);
            return;
        }
        //枚举当前第k个位置上该放哪个数字 枚举这个数字的下标
        for(int i=0;i<n;i++){ 
            if(!vis[i]){
                vis[i] = true; //标记用过
                path.push_back(nums[i]); //放入这个数
                dfs(nums,k+1);
                path.pop_back(); //回溯
                vis[i] = false;
            }
        }
    }
};

47. 全排列 II

与leetcode46比较,这题数字可能重复

需要判重

A.采用枚举每个数在哪个位置
B.判重的思路:

1.对nums数组进行排序

2.dfs搜索的时候需要保证值相同的数不在同一个位置上(从下一个位置开始)搜索

class Solution {
public:
    int n;
    vector<vector<int>> result;
    vector<int> path;
    vector<bool> visit;

    vector<vector<int>> permuteUnique(vector<int>& nums) {
        n = nums.size();
        visit = vector<bool>(n);
        path = vector<int>(n);

        sort(nums.begin(),nums.end());

        dfs(nums,0,0);

        return result;
    }

    //对一个数枚举该放哪个位置  即枚举位置
    void dfs(vector<int>& nums,int k,int start){
        if(k == n){ //递归出口
            result.push_back(path);
            return;
        }
        //枚举num[k]放的位置在哪
        for(int i=start;i<n;i++){ //从start开始枚举
            if(!visit[i]){
                visit[i] = true;
                path[i] = nums[k];
            //去重关键: 
			//对相邻重复元素枚举的位置从下一个位置开始 而不从0开始
                if(k+1 <n && nums[k+1] == nums[k]){ 
                    dfs(nums,k + 1,i+1);
                }else{
                    dfs(nums,k + 1,0);
                }
                visit[i] = false;
            }
        }
    }
};

78. 子集

给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。

递归考虑顺序:按顺序,枚举每个数两种状态:选、不选

方法一:dfs

class Solution {
public:
    int n;
    vector<int> path;
    vector<vector<int>> result;
    vector<vector<int>> subsets(vector<int>& nums) {
        n = nums.size();
        dfs(0,nums);
        return result;
    }
    void dfs(int k,vector<int>& nums){
        if(k == n){
            result.push_back(path);
            return;
        }
        dfs(k+1,nums);
        path.push_back(nums[k]);
        dfs(k+1,nums);
        path.pop_back();
    }
};

方法2:二进制枚举:i从0到2^n-1,位置上是1的时候选,0的时候不选

class Solution {
public:
    vector<vector<int>> subsets(vector<int>& nums) {
        vector<vector<int>> result;
        //二进制枚举 每个数代表一种方案
        for(int i=0;i< 1<<nums.size() ;i++){ 
            vector<int> now;
            //枚举每一位 位数一共nums.size()
            for(int j=0;j<nums.size();j++){ 
                //如果第j位上数字为1就表示选这个数
                if(i >> j & 1){ 
                    now.push_back(nums[j]);
                }
            }
            result.push_back(now);
        }
        return result;
    }
};

90. 子集 II

与leetcode78的区别:数字可以重复

枚举的时候,再枚举这个数的选多少个

class Solution {
public:
    int n;
    vector<vector<int>> result;
    vector<int> path;
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        n = nums.size();
        sort(nums.begin(),nums.end()); //先排序
        dfs(nums,0);
        return result;
    }
    void dfs(vector<int>& nums,int k){
        if(k == n){
            result.push_back(path);
            return;
        }

        //统计后面与当前元素相同的个数
        int cnt = 0; 
        while(k + cnt < n && nums[k + cnt] == nums[k]) cnt++;

        for(int i=0;i<=cnt;i++) {
            dfs(nums,k + cnt);
            path.push_back(nums[k]);
        }
        for(int i=0;i<=cnt;i++) path.pop_back();
    }
};

216. 组合总和 III

找出所有相加之和为 n 的 k个数的组合

组合中只允许含有 1 - 9 的正整数

并且每种组合中不存在重复的数字。

class Solution {
public:
    vector<vector<int>> result;
    vector<int> path;
    vector<vector<int>> combinationSum3(int k, int n) {
        dfs(k,1,n);
        return result;
    }

    //当前还剩k个没选  选到了数字start上  总和还需要n
    void dfs(int k,int start,int n){
        if(k == 0){
            if(n == 0) result.push_back(path);
            return;
        }
        for(int i = start; i <= 9; i++ ){
            path.push_back(i);
            //剩k-1个没选 选到i+1了 总和还需要n-i
            dfs(k-1,i+1,n-i);
            path.pop_back();
        }
    }
};

剪枝:因为还需要选k个,i<=9即最大值可以替换为 i<=9 + 1 - k个

for(int i = start; i <= 10-k; i++ )

52. N皇后 II

方法一:枚举每一个坐标放不放棋子

class Solution {
public:
    int ans;
    vector<bool> col,row,diag,anti_diag;
    int totalNQueens(int n) {
        row = col = vector<bool>(n,false);
        diag = anti_diag = vector<bool>(2*n,false);
        ans = 0;
        dfs(0,0,0,n);
        return ans;
    }

    //当前搜索到的坐标x,y  当前已选择的皇后个数  
    void dfs(int x,int y,int k,int n){
        if(y == n) x++, y = 0;
        if(x == n){
            if(k == n) ans++;
            return;
        }
        dfs(x,y+1,k,n); //不选这个位置
        if(!row[x] && !col[y] && !diag[x+y] && !anti_diag[n+x-y]){
            row[x] = col[y] = diag[x+y] = anti_diag[n+x-y] = true;
            dfs(x,y+1,k+1,n);
            row[x] = col[y] = diag[x+y] = anti_diag[n+x-y] = false;
        }

    }
};

方法二:枚举每一行放在哪个位置,只需枚举行

37. 解数独

开三个标记数组,分别标记行、列、所在九宫格是否用过某个数

1.先初始化,统计原棋盘已经放的元素。

2.dfs搜索每一个坐标点,枚举该坐标点下能放哪个数字,要求所在行、所在列、所在九宫格都没有这个数字,

3.递归下一个子节点,递归完true就结束程序;否则回溯这个点

image-20200526220901205

52和37的小结:

Dancing Links解决 精确覆盖问题

十字链表

473. 火柴拼正方形

剪枝优化题

超时代码:时间复杂度n^4

class Solution {
public:
    int n;
    int ave;
    vector<bool> vis;
    bool makesquare(vector<int>& nums) {
        n = nums.size();
        vis = vector<bool>(n,false);
        sort(nums.begin(),nums.end());
        reverse(nums.begin(),nums.end());
        long long sum = 0;
        int cnt = 0;
        for(int i=0;i<n;i++){
            sum += nums[i];
        }
        if(sum == 0 || sum % 4 != 0 ) return false;
        for(int i=0;i<n;i++){
            if(nums[i] > sum/4) return false;
            if(nums[i] == sum/4) {
                cnt++;
                vis[i] = true;
            }
        }
        if(cnt == 4 && n == 4) return true; 
        for(int i=0;i<n;i++){
            if(nums[i] >= sum/4) continue;
        }
        ave = sum/4;
        return dfs(4-cnt,0,nums);
    }

    bool dfs(int k,int ans,vector<int>& nums){
        if(k == 0){ //递归出口
            for(int i=0;i<n;i++){
                if(!vis[i]) return false;
            }
            return true;
        }
        if(ans > ave) return false; //剪枝

        if(ans == ave) return dfs(k-1,0,nums); 
        //枚举这一轮选的数
        for(int i=0;i<n;i++){
            if(vis[i]) continue;
            vis[i] = true;
            if(dfs(k,ans+nums[i],nums)) return true;
            vis[i] = false;
        }
        return false;
    }
};

剪枝优化代码:

posted @ 2020-05-26 23:06  fishers  阅读(344)  评论(0编辑  收藏  举报