leetcode:深度优先搜索(DFS)和广度优先搜索(BFS)

DFS的解答步骤

  1. 定义全局的存放所有解的对象和存放1个解的对象;
  2. 在递归函数中,确定递归终止条件,并在终止时提交一个完整的解;
  3. 递归函数中的代码结构应该是:执行动作、调用递归、撤销动作。

51. N皇后问题

在一个N*N的棋盘上放置N个皇后,每行一个并使其不能互相攻击(同一行、同一列、同一斜线上的皇后都会自动攻击)。

思路
采用递归不断处理子问题。
每次在棋盘上的当前行放置1个皇后,就需要在该行枚举所有情况,当不与前面行的皇后冲突时,就可以递归处理剩下位置。
当到达最后一行的时候,保存结果,递归结束。

关键问题是如何检测在某行某列放置皇后的时候是否造成冲突。

其实,由于每1行只允许放置1个皇后,因此可以采用1个长度为n的整形数组记录每行放置皇后的列位置,这样空间复杂度就降低为O(n)了。然后,在检查当前位置的皇后是否与之前的皇后冲突时,就需要遍历之前的皇后,以检测是否冲突。
由于按行递归,已经限制了皇后都不在同一行,因此只需检测列值是否相同,以及行列差的绝对值是否相同(判断是否在同一斜线)。

class Solution {
public:
    vector<vector<string>> solveNQueens(int n) {
        if(n<=0) return res;
        colSite.resize(n,-1);
        dfs(0,n);
        return res;
    }
private:
    vector<vector<string>> res;//存放所有的答案
    vector<int> colSite;//存放每行的皇后所在的列位置信息

    void dfs(int c,const int N) 
    {
        if(c == N)
        {//如果放置完N个皇后,就完成了一个解,保存该结果到res
            vector<string> frame(N,string(N,'.'));
            for(int row=0;row<N;row++)
                frame[row][colSite[row]] = 'Q';
            res.push_back(frame);
            return;
        }

        bool valid=true;
        for(int col=0;col<N;col++) 
        {
            valid=true;
            //判断将第c行上的Q放在第col列上是否合法?
            for(int row=0;row<c;row++) 
            {
                //对于之前已经确定的c-1行上的皇后,如果和当前的col同一列,或者在对角线(行-行 == 列-列)上,则不合法
                if(colSite[row]==col || abs(c-row)==abs(col-colSite[row])) 
                {
                    valid=false;
                    break;
                }
            }
            if(valid) 
            {
                //如果将第c行上的Q放在第col列上是合法的,就执行-递归-撤销
                colSite[c]=col;
                dfs(c+1,N);
                colSite[c]=-1;
            }
        }
    }
};

78. 子集

已知一个数集,求它的所有子集。
例如:nums = [1,2,3],它的所有子集是:

[
  [3],
  [1],
  [2],
  [1,2,3],
  [1,3],
  [2,3],
  [1,2],
  []
]

思路
同样是很常规的DFS,唯一需要注意的就是,每个解的元素都应该是递增的。

PS:这道题还可以使用位运算。例如[1,2,3],定义每个元素有个标志位,全集为1 1 1,全集表示都取,它的子集有1 0 1 、 1 1 0 、 1 0 0等等,我们通过枚举子集,然后再通过子集获取原来数组的元素即可。

class Solution {
public:
    vector<vector<int>> res;
    vector<int> r;
    vector<int> book;

    vector<vector<int>> subsets(vector<int>& nums) {
        book.resize(nums.size(),0);
        for(int i=0;i<=nums.size();i++) {
            dfs(nums,0,i);
        }
        return res;

    }

    void dfs(vector<int>& nums,int count,int length) {
        if(count == length) res.push_back(r);

        for(int i=0;i<nums.size();i++) {
            if(book[i]==1) continue;
            if(r.size()==0 || nums[i]>r[r.size()-1]) 
            {
                book[i]=1;
                r.push_back(nums[i]);
                dfs(nums,count+1,length);
                r.pop_back();
                book[i]=0;
            }
        }   
    }
};

46. 给定元素的全排列

思路
也是典型的DFS问题,同样用到了递归。

需要一个二维的vector对象,用来保存所有可能的排列;需要一个一维的vector对象,用来保存1次完整的解;需要一个set,存储元素是否已被使用的信息,方便查找。

class Solution {
public:
    vector<vector<int>> permute(vector<int> &nums) {
        if(nums.empty()) return result;
        book.resize(nums.size(),0);
        dfs(0, nums);
        return result;
    }
private:
    vector<vector<int>> result;//存储所有可能的排列
    vector<int> oneResult;//1次解
    vector<int> book;//已经使用的元素

    void dfs(int s, vector<int> &nums){
        if(s == nums.size()) result.push_back(oneResult);

        for(int i = 0; i < nums.size(); i++){
            if(book[i])   
                continue;//如果元素已经存在,就pass
            //执行动作
            book[i]=1;
            oneResult.push_back(nums[i]);
            //调用递归
            dfs(s + 1, nums);
            //撤销动作
            book[i]=0;
            oneResult.pop_back();
        }
    }
};

216. 元素之和一定时的所有组合

Find all possible combinations of k numbers that add up to a number n, given that only numbers from 1 to 9 can be used and each combination should be a unique set of numbers.

Ensure that numbers within the set are sorted in ascending order.

Example 1:
Input: k = 3, n = 7
Output:[[1,2,4]]

Example 2:
Input: k = 3, n = 9
Output:[[1,2,6], [1,3,5], [2,3,4]]

思路
这道题的特点是在搜索时,当前值必须比前面已经取的值大。

class Solution {
public:
    vector<vector<int>> combinationSum3(int k, int n) {
        if(k<1 || n<1) return res;
        combination(0,0,k,n);
        return res;
    }

private:
    vector<vector<int>> res;
    vector<int> oneres;
    int book[15]={0};

    void combination(int s,int sum,int k,int n){
        //递归终止条件,提交1个解
        if(s==k)
        {
            if(sum==n)
                res.push_back(oneres);
            return;
        }
        //新加入的元素要比前一个元素大
        //DFS核心部分
        for(int i=1;i<10;i++)
        {
            if(book[i]==0 && (oneres.size()==0 || i>oneres[oneres.size()-1]))
            {
                book[i]=1;
                oneres.push_back(i);
                combination(s+1,sum+i,k,n);
                oneres.pop_back();
                book[i]=0;
            } 
        }
    }
};

77. 1~n中任取k个数,所有组合

Given two integers n and k, return all possible combinations of k numbers out of 1 … n.

For example,
If n = 4 and k = 2, a solution is:

[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]
思路
与上一题类似。

class Solution {
public:
    vector<vector<int>> combine(int n, int k) {
        if(k == 0 || n == 0)    return result;
        book.resize(n+5,0);
        dfs(n,0,k);
        return result;
    }

    vector<vector<int>> result;
    vector<int> oneresult;
    vector<int> book;

    void dfs(int n,int length,int k)
    {
    //递归终止,提交1个解
        if(length==k) 
        {
            result.push_back(oneresult);
            return;
        }

         for(int i=1;i<=n;i++)
         {
             //当前元素没有被使用,而且比前一个元素(如果有的话)大
             if(book[i]==0 && (oneresult.size()==0 || i>oneresult.back()))
             {
                 book[i]=1;
                 oneresult.push_back(i);
                 dfs(n,length+1,k);
                 oneresult.pop_back();                 
                 book[i]=0;
             }

         }
        }
};

301. 删除非法的括号(Remove Invalid Parentheses)

Remove the minimum number of invalid parentheses in order to make the input string valid. Return all possible results.

Note: The input string may contain letters other than the parentheses ( and ).

Examples:
“()())()” -> [“()()()”, “(())()”]
“(a)())()” -> [“(a)()()”, “(a())()”]
“)(” -> [“”]
思路
DFS和BFS都可以解决。

首先是DFS,其思想是去掉一个括号,然后计算其失配括号是否小于原来的字符串,如果不小于,则说明去掉这个括号并不能获得一个更好的解,则不再继续此分支(剪枝)。
相反,如果去掉一个括号之后的失配括号变少了,则说明去掉这个括号是有效的,可以继续此分支。
注意:
在遍历的时候可能会碰到去掉一个括号得到和以前一样的字符串,这种情况需要跳过,不然会得到重复的解。

class Solution {
public:
    vector<string> ans;
    set<string> Set;
    vector<string> removeInvalidParentheses(string s) {
        dfs(s,nomatch(s));
        return ans;
    }

    void dfs(string str,int num)
    {
        if(num==0)//如果完全匹配上
        {
            ans.push_back(str);
            return;
        }
        for(int i=0;i<str.size();i++)
        {
            string sub = str.substr(0,i) + str.substr(i+1);
            if(Set.count(sub)==0)
            {
                Set.insert(sub);
                int no = nomatch(sub);
                if(no<num)
                    dfs(sub,no);
            }
            else
                continue;
        }
    }

    int nomatch(string s)
    {
        int num=0;
        stack<int> st;
        for(int i=0;i<s.size();i++)
        {
            if(s[i]=='(')
                st.push(i);
            else if(s[i]==')')
                if(st.empty())
                    num++;//')'遇到了失配的情况
                else
                    st.pop();
        }
        num += st.size();//还要考虑到'('失配的情况
        return num;
    }
};

如果采用BFS,思路是这样的:
如果一个字符串中的括号不能完全匹配,就依次去掉其中一个括号,将新字符串依次加入队列;
根据题意,如果找到了第一个匹配的解,那么比该字符串更短的字符串都不可能是解了。因此,可以剪枝了。否则,继续去掉括号,放入队列中;
当队列为空,BFS终止。

注意这里要定义一个标志位found,表示在当前层(某层中删除的括号都是x个),已经出现了一个匹配的字符串了。那么下面就无需再尝试删除了。所有可能的解都可以从当前queue中得到。当前queue中保存的,是所有可能的解。

class Solution {
public:
    vector<string> ret;

    vector<string> removeInvalidParentheses(string s) {
        set<string> Set;
        queue<string> q;

        q.push(s);
        Set.insert(s);

        bool found=false;
        while( !q.empty() ) {
            //取出queue中的字符串
            string str=q.front();
            q.pop();
            //如果合法
            if(isValid(str)) {
                ret.push_back(str);
                found=true;
                continue;
            }
            //如果成功找到一个解,则跳过后面对q中子串的所有尝试删除操作
            if(found) continue;
            //如果该字符串不是解,就除去其中一个括号
            for(int i=0;i<str.length();i++) {
                //定位到括号的位置
                if(str[i]!='(' && str[i]!=')') continue;
                //将这个括号从字符串中去除
                string sub=str.substr(0,i) + str.substr(i+1);
                //如果该字符串没有重复
                if(!Set.count(sub)) 
                {
                    q.push(sub);
                    Set.insert(sub);
                }
            }
        }
        return ret;
    }
    //判断字符串中的括号是否匹配成功
    bool isValid(string s) 
    {
        int count=0;
        for(int i=0;i<s.length();i++) 
        {
            if(s[i]=='(')
                count++;
            else if(s[i]==')')
            {
                if(count==0)
                    return false;
                count--;
            }
        }
        return count==0;
    }

};

131. 将字符串分割成回文字符串

给定一个字符串,将其分割为若干个回文子串集合。每个集合中的所有元素均为回文串,且元素连接结果为源串。
思路:
递归。从s.substring(0,1)开始,到s.substring(0,s.length()),如果开头的子串是回文字符串,就将它存入list中,并对剩余的子串s.substring(i)同样调用dfs方法。

class Solution {
public:
    vector<vector<string>> partition(string s) {
        dfs(s);
        return ans;
    }

    vector<vector<string>> ans;
    vector<string> one;

    void dfs(string s)
    {
        if(s.size()==0)
        {
            ans.push_back(one);
            return;
        }
        for(int i=1;i<=s.size();i++)
        {
            string sub = s.substr(0,i);
            if(is(sub))
            {
                one.push_back(sub);
                dfs(s.substr(i));
                one.pop_back();
            }
        }
    }
    //判断是否是回文字符串
    bool is(string s)
    {
        int p=0,q=s.size()-1;
        for(int i=0;i<s.size();i++)
        {
            if(s[p] != s[q])
                return false;
            p++;
            q--;
        }
        return true;
    }
};

求解数独

数独(Sudoku)是一种运用纸、笔进行演算的逻辑游戏。玩家需要根据9×9盘面上的已知数字,推理出所有剩余空格的数字,并满足每一行、每一列、每一个粗线宫内的数字均含1-9,不重复。 每一道合格的数独谜题都有且仅有唯一答案,推理方法也以此为基础,任何无解或多解的题目都是不合格的。
《leetcode:深度优先搜索(DFS)和广度优先搜索(BFS)》
数独的基本解法就是利用规则的摒弃法:
每一行称为数独的行,每一列称为数独的列,每一个小九宫格称为数独的宫。数独的基本规则就是每一行、每一列、每一宫中,1-9这9个数字都只出现一次。

#include<iostream> 
using namespace std;

/* 构造完成标志 */
bool sign = false;

/* 创建数独矩阵 */
int num[9][9];

/* 函数声明 */
void Input();
void Output();
bool Check(int n, int key);
int DFS(int n);

/* 主函数 */
int main()
{
    cout << "请输入一个9*9的数独矩阵,空位以0表示:" << endl;
    Input();
    DFS(0);
    Output();
    system("pause");
}

/* 读入数独矩阵 */
void Input()
{
    char temp[9][9];
    for (int i = 0; i < 9; i++)
    {
        for (int j = 0; j < 9; j++)
        {
            cin >> temp[i][j];
            num[i][j] = temp[i][j] - '0';
        }
    }
}

/* 输出数独矩阵 */
void Output()
{
    cout << endl;
    for (int i = 0; i < 9; i++)
    {
        for (int j = 0; j < 9; j++)
        {
            cout << num[i][j] << " ";
            if (j % 3 == 2)
            {
                cout << " ";
            }
        }
        cout << endl;
        if (i % 3 == 2)
        {
            cout << endl;
        }
    }
}

/* 判断key填入n时是否满足条件 */
bool Check(int n, int key)
{
    /* 判断n所在行是否合法 */
    for (int i = 0; i < 9; i++)
    {
        /* j为n的横坐标 */
        int j = n / 9;
        if (num[j][i] == key) return false;
    }

    /* 判断n所在列是否合法 */
    for (int i = 0; i < 9; i++)
    {
        /* j为n的纵坐标 */
        int j = n % 9;
        if (num[i][j] == key) return false;
    }

    /* x为n所在的小九宫格左顶点的横坐标 */
    int x = n / 9 / 3 * 3;

    /* y为n所在的小九宫格左顶点的纵坐标 */
    int y = n % 9 / 3 * 3;

    /* 判断n所在的小九宫格是否合法 */
    for (int i = x; i < x + 3; i++)
    {
        for (int j = y; j < y + 3; j++)
        {
            if (num[i][j] == key) return false;
        }
    }

    /* 全部合法,返回正确 */
    return true;
}

/* 用DFS求解数独 */
int DFS(int n)
{
    /* 所有的都符合,退出递归 */
    if (n > 80)
    {
        sign = true;
        return 0;
    }
    /* 当前位不为空时跳过 */
    if (num[n/9][n%9] != 0)
    {
        DFS(n+1);
    }
    else
    {
        /* 否则对当前位进行枚举测试 */
        for (int i = 1; i <= 9; i++)
        {
            /* 满足条件时填入数字 */
            if (Check(n, i) == true)
            {
                num[n/9][n%9] = i;
                /* 继续搜索 */
                DFS(n+1);
                /* 返回时如果求解成功,则直接退出,因为数独的解总是唯一的,没有必要再进行下去 */
                if (sign == true) return 0;
                /* 如果构造不成功,还原当前位 */
                num[n/9][n%9] = 0;
            }
        }
    }
}

4个数算24点的全排列

随机给你四张牌,包括A(1),2,3,4,5,6,7,8,9,10,J(11),Q(12),K(13)。要求只用’+’,’-‘,’*’,’/’运算符以及括号改变运算顺序,使得最终运算结果为24(每个数必须且仅能用一次)。游戏很简单,但遇到无解的情况往往让人很郁闷。你的任务就是针对每一组随机产生的四张牌,判断是否有解。我们另外规定,整个计算过程中都不能出现小数。
思路
对于a,b,c,d四个数进行+,-,*,/。
有这几种情况:

  1. (a@b)@(c@d),(c@d)@(a@b),(b@a)@(c@d),… 综合为(x1@y1)@(x1@y1)这种情况,不过得到的值可以为-24
  2. ((a@b)@c)@d,a@((b@c)@d),(d@(a@b)@c),…综合为((x1@y1)@x2)@y2这种情况,不过得到的值可以为-24

所以,思路是先求a,b,c,d的全排列,再用(a@b)@(c@d),((a@b)@c))@d去计算即可。
注意,用到了next_permutation求全排列。由于该函数求的是按字典序的全排列,所以要先进行排序。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
using namespace std;
int flag;
int num[4];
int cmp(const void *a,const void *b)
{
    return *(int *)a-*(int *)b;
}
void dfs(int sum,int cur,int m)
{
    if(flag)
    return;
    if(m==3)
    {
        if(sum+cur==24||sum-cur==24||sum*cur==24)
        flag=1;
        if(cur!=0&&sum%cur==0&&sum/cur==24)
        flag=1;
        return;
    }
    dfs(sum+cur,num[m+1],m+1);  //先计算前一部分
    dfs(sum-cur,num[m+1],m+1);
    dfs(sum*cur,num[m+1],m+1);
    if(cur!=0&&sum%cur==0)
    dfs(sum/cur,num[m+1],m+1);
    dfs(sum,cur+num[m+1],m+1);  //先计算后一部分,相当于加括号
    dfs(sum,cur-num[m+1],m+1);
    dfs(sum,cur*num[m+1],m+1);
    if(num[m+1]!=0&&cur%num[m+1]==0)
    dfs(sum,cur/num[m+1],m+1);
}
int main()
{
    int i;
    char str[5];
    //处理4个数的输入
    while(scanf("%s",str)!=EOF)
    {
        if(strlen(str)==2)
        num[0]=10;
        else
        {
            if(str[0]==’A') num[0]=1; else if(str[0]==’J')
            num[0]=11;
            else if(str[0]==’Q') num[0]=12; else if(str[0]==’K')
            num[0]=13;
            else
            num[0]=str[0]-’0′;
        }
        for(i=1;i<=3;i++)
        {
            scanf("%s",str);
            if(strlen(str)==2)
            num[i]=10;
            else
            {
               if(str[0]==’A') num[i]=1; else if(str[0]==’J')
               num[i]=11;
               else if(str[0]==’Q') num[i]=12; else if(str[0]==’K')
               num[i]=13;
               else
               num[i]=str[0]-’0′;
            }
        }
        qsort(num,4,sizeof(num[0]),cmp);
        flag=0;
        do
        {
            dfs(num[0],num[1],1);
        }while(next_permutation(num,num+4)&&!flag);
        if(flag)
        printf("Yes\n");
        else
        printf("No\n");
    }
    return 0;
}
    原文作者:BFS
    原文地址: https://blog.csdn.net/jinzhao1993/article/details/50830238
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞