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 > solveNQueens(int n) {
        int* a = new int[n];
        //初始化
        for(int k = 0 ; k < n ; k++)
            a[k] = -100;
        int j = 0,i = 0;
        vector> 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> &result, int *a,int n)
    {
        vector 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> &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 > 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> 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> & ans, vector tmp,vector &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 > subsets(vector &S) {
        // Start typing your C/C++ solution below
        // DO NOT write int main() function
        sort(S.begin(),S.end());
        int n = S.size();
       
        vector tmp;
        vector> 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)