Leetcode中的回溯法题目总结:八皇后问题; unique path问题;subsets问题

回溯法,采用试错的思想,分步的去解决一个问题。在分步解决问题的过程中,当它通过尝试发现现有的分步答案不能得到有效的正确解答的时候,就会取消上一步或者上几步的运算,再通过其他的可能分步解答再次尝试寻找问题的答案。最经典的问题,就是八皇后问题。

1 n皇后问题

 

n-皇后 问题就是正确的在棋盘上面放置皇后的位置,从而使得任意两个皇后之间都无法攻击对方,攻击的方式是同行、同列或对角线。

给定 n, 要求返回n-皇后问题的所有解。

Each solution contains a distinct board configuration of the n-queens' placement, where 'Q' and '.' both indicate a queen and an empty space respectively.

举例,对于n为4的情况,存在如下这些解

[

 [".Q..",  // Solution 1

  "...Q",

  "Q...",

  "..Q."],



 ["..Q.",  // Solution 2

  "Q...",

  "...Q",

  ".Q.."]

]

首先我们需要一个棋盘用来保存当前的状态,也就是那里已经放置了皇后,从而在进入新的一行的时候进行判断。棋盘状态可以用n*n的矩阵保存,但是也可以使用1*n的数组,数组的下标表示棋盘的行数,数组的值便是棋盘的列数,这样就充分的表示了哪行哪列可以放置皇后。算法的结构如下:

1 初始化棋盘数组

2 行数i: 1->n

列j: 1->n 

对于每一个位置(i,j),检测是否可以放皇后,检测的原则是当前的列数不与之前放置的皇后的列数相等,同时也不能处于其对角线上,这里要注意的是,

如果在对角线上,就要满足如下性质:|i - row| == |j - col|

如果不能放皇后,就列数后移一位。

如果可以放皇后,就更新棋盘数组,同时行数下移一位。

如果第i行,没有找到放置皇后的位置。

如果i ==0, 说明已经回溯到第一行了,找到了所有的可能的解,退出。

如果i != 0, 那就把上一行的皇后位置向后移动一列,然后继续在这一行寻找合法的皇后位置。

 如果行数到达了最后一行,说明一个解已经找到,回溯,寻找另外一个解。


这里首先给出一个非递归的解法:

vector<vector<string> > solveNQueens(int n) {

        int* a = new int[n];

        //初始化

        for(int k = 0 ; k < n ; k++)

            a[k] = -100;

        int j = 0,i = 0;

        vector<vector<string>> result;

       

        while(i < n)

        {

            while(j < n)

            {

                if(valid(i,j,a))

                {

                    a[i] = j;

                    j = 0;//mark

                    break;

                }

                else

                {

                    j++;

                }

            }

            //如果第i行没有找到可以放置皇后的位置

            if(a[i] == -100)

            {

                // 回溯到第一行,还是没找到可行的解,那就说明都遍历了

                if(i == 0)

                    break;

                // 否则的话回溯,并把上一行的皇后清空,再往后移动一位

                else

                {

                    --i;

                    j = a[i] + 1;

                    a[i] = -100;

                    continue;

                }

            }         

            //这个时候已经找到了全部需要的皇后

            if(i == n -1)

            {

                pushIn(result,a,n);

                //但是由于还没完全返回全部的组合,所以还要继续找

                j = a[i] + 1;

                a[i] = -100;

                continue;

            }

            i++;

        }

        return result;

    }

判断是否合法的函数:

 

 

    bool  valid(int row, int col, int *a)

    {

        for(int i = 0; i < row; i++)//a中之前的行

        {

            if(a[i] == col || abs(row - i) == abs(col - a[i]))

            {

                return false;

            }

        }

        return true;

    }


把结果装入:

 

 

    void pushIn(vector<vector<string>> &result, int *a,int n)

    {

        vector<string> tmp1;

        for(int i = 0; i < n; i++)

        {

            string tmp2;

            for(int j = 0; j < n ; j++)

            {

                if(a[i] == j)

                {

                    tmp2+="Q";

                }

                else tmp2+=".";

            }

            tmp1.push_back(tmp2);

        }

        result.push_back(tmp1);

    }


 

再给出一个递归的解法:

 

    void queen(int row, int *a, int n,vector<vector<string>> &result)

    {



        if (n == row)      //如果已经找到结果,则打印结果

            pushIn(result,a,n);

        else

        {

              for (int k=0; k < n; k++)

              { //试探第row行每一个列

                  if (valid(row, k,a))

                  {

                      a[row] = k;   //放置皇后

                      queen(row + 1,a,n,result);  //继续探测下一行

    //返回上一级的时候要重新初始化,

                        a[row] = -100;

                  }

              }     

        }

    }

 

vector<vector<string> > solveNQueens(int n) {

        // Start typing your C/C++ solution below

        // DO NOT write int main() function

        int* a = new int[n];

        //初始化

        for(int k = 0 ; k < n ; k++)

            a[k] = -100;

        vector<vector<string>> result;

        queen(0,a,n,result);

        return result;

    }


 


2 Unique Path独一无二的路

 

 

一个机器人处在一个m*n的方格区的左上角。机器人只能向下或者向右走。机器人的最终目的是右下角。求出一共有多少可能的独一无二的路径?

Note: m and n will be at most 100.

由于每次都只能向下或向右走,所以当前节点i,j 处的路径数应该等于其上部的节点i-1,j 以及其左部的节点i,j-1 的路径数之和。

path(i,j) = path(i-1,j) + path(i,j-1);

先给出一个比较直观的递归的解法:

 

int backtrack(int r, int c, int m, int n) {

    if (r == m && c == n)

        return 1;

    if (r > m || c > n)

        return 0;



    return backtrack(r+1, c, m, n) + backtrack(r, c+1, m, n);}

 


但是递归的话因为会有一些重复计算,所以效率不是太高。所以需要引入动态规划,保存每一个节点处的路径,从而避免重复计算,提升算法效率。

 

 

const int M_MAX = 100;const int N_MAX = 100;



int backtrack(int r, int c, int m, int n, int mat[][N_MAX+2]) {

    if (r == m && c == n)

        return 1;

    if (r > m || c > n)

        return 0;

//只要当没有计算的时候,才进行运算,否则的话就直接跳过。两个方向都计算了之后,再把结果给加起来。

    if (mat[r+1][c] == -1)

        mat[r+1][c] = backtrack(r+1, c, m, n, mat);

    if (mat[r][c+1] == -1)

        mat[r][c+1] = backtrack(r, c+1, m, n, mat);



    return mat[r+1][c] + mat[r][c+1];}



int bt(int m, int n) {

    int mat[M_MAX+2][N_MAX+2];

    for (int i = 0; i < M_MAX+2; i++) {

        for (int j = 0; j < N_MAX+2; j++) {

            mat[i][j] = -1;

        }

    }

    return backtrack(1, 1, m, n, mat);}



3 Subsets 求出所有的子集

 

 

给定一系列的数的集合S, 返回所有可能的子集:

注意:

  • 子集中的元素一定要是非递减的。
  • 结果中不能包含重复的子集。

 

举例,
If S = [1,2,3], a solution is:

[

  [3],

  [1],

  [2],

  [1,2,3],

  [1,3],

  [2,3],

  [1,2],

  []

]

这个问题是(n,k)问题的延伸,也就是从n个元素的集合里面选取k个元素,有多少种不同的选法。只要把k从1开始循环到n即可得到答案。
对于(n,k)问题, 不停地选取集合n中的元素,直到个数为k,然后开始回溯,去掉k中尾部的元素,在纳入n中的下一个元素,以此类推,不断回溯,直到遍历所有的可能性。其中使用了栈作为回溯的工具。:
    void subsetsAll(vector<vector<int>> & ans, vector<int> tmp,vector<int> &S, int n,int k,int index)

    {

        if(k == 0)

        {

               if(find(ans.begin(),ans.end(),tmp) == ans.end())

            ans.push_back(tmp);

        }

        else if(k > 0 && index < n)

        {

            tmp.push_back(S.at(index));

            subsetsAll(ans,tmp,S,n,k-1,index+1);//已经找到了第一个,在之后里面寻找k-1个。

           

            tmp.pop_back();

            subsetsAll(ans,tmp,S,n,k,index+1);   //跳过第一个,在之后的里面寻找k个。

        }

    }


主程序:只要把循环改成确定的k,就变成了(n,k)问题。
   vector<vector<int> > subsets(vector<int> &S) {

        // Start typing your C/C++ solution below

        // DO NOT write int main() function

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

        int n = S.size();

       

        vector<int> tmp;

        vector<vector<int>> ans;

        if(n==0) {ans.push_back(tmp); return ans;}

       

        for(int i=0;i<=n;i++)

        {

            subsetsAll(ans,tmp,S,n,i,0);

        }

    }





 















 

你可能感兴趣的:(LeetCode)