DFS查找专题

1.1 Word Search 给定一个二维平板和一个单词,请找出这个单词是否在二维平板中出现。

单词可以由平板中的邻接单元组成,这里的“邻接”定义为上下左右四个方向。
同一个单元上的字母最多只能使用一次。
在这里插入图片描述
在深度优先搜索中,最重要的就是考虑好搜索顺序。
我们先枚举单词的起点,然后依次枚举单词的每个字母。
过程中需要将已经使用过的字母改成一个特殊字母,以避免重复使用字符。

时间复杂度分析:单词起点一共有 n2n2 个,单词的每个字母一共有上下左右四个方向可以选择,但由于不能走回头路,所以除了单词首字母外,仅有三种选择。所以总时间复杂度是 O(n2*3k)

class Solution {
   
public:
    bool exist(vector<vector<char>>& board, string str) {
   
        for (int i = 0; i < board.size(); i ++ )
            for (int j = 0; j < board[i].size(); j ++ )
                if (dfs(board, str, 0, i, j))
                    return true;
        return false;
    }

    bool dfs(vector<vector<char>> &board, string &str, int u, int x, int y) {
   
    	//不匹配直接返回false,进入下一步循环,匹配则继续执行
        if (board[x][y] != str[u]) return false;
        //字符串全部匹配成果则返回true
        if (u == str.size() - 1) return true;
        int dx[4] = {
   -1, 0, 1, 0}, dy[4] = {
   0, 1, 0, -1};
        char t = board[x][y];
        board[x][y] = '*';
        for (int i = 0; i < 4; i ++ ) {
   //每个字母都在四个方向去遍历
            int a = x + dx[i], b = y + dy[i];
            if (a >= 0 && a < board.size() && b >= 0 && b < board[a].size()) {
   
            	//不匹配则回溯回该字母的下一个方向
                if (dfs(board, str, u + 1, a, b)) return true;
            }
        }
        board[x][y] = t;//四个方向都不匹配,则回溯回上一个字母的下一个方向,并恢复现场
        return false;
    }
};

1.2 给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
在这里插入图片描述
(递归) O(4^l)

  • 可以通过手工或者循环的方式预处理每个数字可以代表哪些字母。
  • 通过递归尝试拼接一个新字母。 递归到目标长度,将当前字母串加入到答案中。
  • 注意,有可能数字串是空串,需要特判。
class Solution {
   
public:
    vector<char> digit[10];
    vector<string> res;

    void init() {
   
        char cur = 'a';
        for (int i = 2; i < 10; i++) {
   
            for (int j = 0; j < 3; j++)
                digit[i].push_back(cur++);
            if (i == 7 || i == 9)
                digit[i].push_back(cur++);
        }
    }

    void solve(string digits, int d, string cur) {
   
        if (d == digits.length()) {
   
            res.push_back(cur);
            return;
        }

        int cur_num = digits[d] - '0';

        for (int i = 0; i < digit[cur_num].size(); i++)
            solve(digits, d + 1, cur + digit[cur_num][i]);
    }

    vector<string> letterCombinations(string digits) {
   
        if (digits == "")
            return res;
        init();
        solve(digits, 0, "");
        return res;
    }
};

1.3 给定一个字符串,只包含数字。请解码出所有合法的IP地址。
在这里插入图片描述

(暴力搜索) O(C3n−1)
直接暴力搜索出所有合法方案。
合法的IP地址由四个0到255的整数组成。我们直接枚举四个整数的位数,然后判断每个数的范围是否在0到255。

时间复杂度分析:一共 n个数字,n−1个数字间隔,相当于从 n−1个数字间隔中挑3个断点,所以计算量是 O(C3n−1)

class Solution {
   
public:
    vector<string> ans;
    vector<int> path;

    vector<string> restoreIpAddresses(string s) {
   
        dfs(0, 0, s);
        return ans;
    }

    // u表示枚举到的字符串下标,k表示当前截断的IP个数,s表示原字符串
    void dfs(int u, int k, string &s)
    {
   
        if (u == s.size())
        {
   
            if (k == 4)
            {
   
                string ip = to_string(path[0]);
                for (int i = 1; i < 4; i ++ )
                    ip += '.' + to_string(path[i]);
                ans.push_back(ip);
            }
            return;
        }
        if (k > 4) return;

        unsigned t = 0;
        for (int i = u; i < s.size(); i ++ )
        {
   
            t = t * 10 + s[i] - '0';
            if (t >= 0 && t < 256)
            {
   
                path.push_back(t);
                dfs(i + 1, k + 1, s);
                path.pop_back();
            }
            if (!t) break;
        }
    }
};
2 排列

2.1 给出一列互不相同的整数,返回其全排列。
在这里插入图片描述
(回溯) O(n×n!)
我们从前往后,一位一位枚举,每次选择一个没有被使用过的数。
选好之后,将该数的状态改成“已被使用”,同时将该数记录在相应位置上,然后递归。
递归返回时,不要忘记将该数的状态改成“未被使用”,并将该数从相应位置上删除。

时间复杂度分析:

搜索树中最后一层共 n! 个叶节点,在叶节点处记录方案的计算量是 O(n),所以叶节点处的计算量是 O(n×n!)
搜索树一共有 n!+n!2!+n!3!+…=n!(1+12!+13!+…)≤n!(1+12+14+18+…)=2n! 个内部节点,在每个内部节点内均会for循环 n次,因此内部节点的计算量也是 O(n×n!)。 所以总时间复杂度是 O(n×n!)

class Solution {
   
public:
    vector<vector<int>> res;
    vector<bool> st;
    vector<int> path;
    
    vector<vector<int> > permute(vector<int> &num) {
   
        for(int i=0;i<num.size();i++)
            st.push_back(false);
        dfs(num,0);
        return res;
    }
    
    void dfs(vector<int> &num,int u){
   
        if(u==num.size()){
   
            res.push_back(path);
            return ;
        }
        
        for(int i=0;i<num.size();i++){
   
            if(!st[i]){
   
                st[i]=true;
                path.push_back(num[i]);
                dfs(num,u+1);
                st[i]=false;
                path.pop_back();
            }
        }
    }
};

2.2 给出一组可能包含重复项的数字,返回该组数字的所有排列

(回溯) O(n!)
由于有重复元素的存在,这道题的枚举顺序和 Permutations 不同。

  • 先将所有数从小到大排序,这样相同的数会排在一起;
  • 从左到右依次枚举每个数,每次将它放在一个空位上;
  • 对于相同数,我们人为定序,就可以避免重复计算:我们在dfs时记录一个额外的状态,记录上一个相同数存放的位置start,我们在枚举当前数时,只枚举 start+1,start+2,…,n这些位置。

不要忘记递归前和回溯时,对状态进行更新。
时间复杂度分析: 搜索树中最后一层共 n!个节点,前面所有层加一块的节点数量相比于最后一层节点数是无穷小量,可以忽略。且最后一层节点记录方案的计算量是 O(n),所以总时间复杂度是 O(n×n!)。

class Solution {
   
public:
    vector<bool> st;
    vector<int> path;
    vector<vector<int>> ans;

    vector<vector<int>> permuteUnique(vector<int>& nums) {
   
        sort(nums.begin(), nums.end());
        st = vector<bool>(nums.size(), false);
        path = vector<int>(nums.size());
        dfs(nums, 0, 0);
        return ans;
    }

    void dfs(vector<int>& nums, int u, int start)
    {
   
        if (u == nums.size())
        {
   
            ans.push_back(path);
            return;
        }

        for (int i = start; i < nums.size(); i ++ )
            if (!st[i])
            {
   
                st[i] = true;
                path[i] = nums[u];
                if (u + 1 < nums.size() && nums[u + 1] != nums[u])//排序后相同的数在一起
                    dfs(nums, u + 1, 0);
                else
                    dfs(nums, u + 1, i + 1);
                st[i] = false;
            }
    }
};

如果需要按照字典序排列,则采用方法二

/*枚举每个位置上放哪个数
以[1,1,2]为例:
                                     [ , , ]
                        /               |
               /                        |
           [1, , ]                   [2 ,, ]
        /           \                   |
    /                   \               |
[1,1, ]               [1,2, ]        [2,1, ]
   |                     |              |
[1,1,2]               [1,2,1]        [2,1,1]

*/

class Solution {
   
public:
    vector<bool> st;
    vector<vector<int>> res;
    vector<int> path;
    
    vector<vector<int> > permuteUnique(vector<int> &num) {
   
        sort(num.begin(),num.end());
        int n=num.size();
        st=vector<bool> (n,false);
        path=vector<int> (n);
        if(n==0)return res;
        dfs(num,0);
        return res;
    }
    
    void dfs(vector<int> &num,int u){
   
        
        if(u==num.size()){
   
            res.push_back(path);
            return ;
        }
        
        for(int i=0;i<num.size();i++){
   
            if(i>0&&st[i-1]&&num[i-1]==num[i])
                continue;
            if(!st[i]){
   
                st[i]=true;
                path[u]=num[i];
                dfs(num,u+1);
                st[i]=false;
            }
        }
    }
};

2.3 排列序列
在这里插入图片描述
(计数) O(n2)
做法:
从高位到低位依次考虑每一位;
对于每一位,从小到大依次枚举未使用过的数,确定当前位是几;
为了便于理解,我们这里给出一个例子的具体操作:n=4,k=14。
首先我们将所有排列按首位分组:
1 + (2, 3, 4的全排列)
2 + (1, 3, 4的全排列)
3 + (1, 2, 4的全排列)
4 + (2, 3, 4的全排列)
接下来我们确定第 k=14个排列在哪一组中。每组的排列个数是 3!=6个,所以第14个排列在第3组中,所以首位已经可以确定,是3。

然后我们再将第3组的排列继续分组:
31 + (2, 4的全排列)
32 + (1, 4的全排列)
34 + (1, 2的全排列)
接下来我们判断第 k=14 个排列在哪个小组中。我们先求第 14个排列在第三组中排第几,由于前两组每组有6个排列,所以第14个排列在第3组排第 14−6∗2=2。
在第三组中每个小组的排列个数是 2!=2个,所以第 k个排列在第1个小组,所以可以确定它的第二位数字是1。
依次类推,可以推出第14个排列是 3142。
时间复杂度分析:两重循环,所以时间复杂度是 O(n2)。

class Solution {
   
public:

    string getPermutation(int n, int k) {
   
        string res;
        vector<bool>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值