【算法】剪枝&回溯

剪枝&回溯

劍12 矩陣中的路徑@@

請設計一個函數,用來判斷在一個矩陣中是否存在一條包含某字符串所有字符的路徑。路徑可以從矩陣中的任意一格開始,每一步可以在矩陣中向左、右、上、下移動一格。如果一條路徑經過了矩陣的某一格,那麼該路徑不能再次進入該格子。例如,在下面的3×4的矩陣中包含一條字符串“bfce”的路徑(路徑中的字母用加粗標出)。

[[“a”,“b”,“c”,“e”],
[“s”,“f”,“c”,“s”],
[“a”,“d”,“e”,“e”]]

但矩陣中不包含字符串“abfb”的路徑,因爲字符串的第一個字符b佔據了矩陣中的第一行第二個格子之後,路徑不能再次進入這個格子。

示例 1:

輸入:board = [[“A”,“B”,“C”,“E”],[“S”,“F”,“C”,“S”],[“A”,“D”,“E”,“E”]], word = “ABCCED”
輸出:true
示例 2:

輸入:board = [[“a”,“b”],[“c”,“d”]], word = “abcd”
輸出:false

來源:力扣(LeetCode)

分析:經典回溯,但此題只要求返回true or false,能返回所有路徑嗎?

friend ostream &operator<<(ostream &out, const vector<vector<char>> &board)
{
//重載 << ,用於調試輸出二維數組
    for (auto i : board)
    {
        for (auto j : i)
        {
            out << j << " ";
        }
        out << endl;
    }
    out << endl;
    return out;
}
bool backtrack(vector<vector<char>> &board, int row, int col,
               const string &word, int idx)
{
    //正確返回終止條件,找到一個正確分支即終止
    if (idx == word.size())
        return true;
    //數組越界終止條件
    if (row < 0 || row >= board.size() ||
        col < 0 || col >= board[0].size())
        return false;
    //如果表格當前字符不等於欲匹配字符,剪掉此分支
    //這樣還在增長的分支就是可能正確的分支
    if (word[idx] != board[row][col])
        return false;
    //設置標記位,如果後續回到此位置,比對字符即知
    board[row][col] = '*';
    //往四個方向走,有一個方向返回true,程序返回true
    if (backtrack(board, row - 1, col, word, idx + 1) ||
        backtrack(board, row + 1, col, word, idx + 1) ||
        backtrack(board, row, col - 1, word, idx + 1) ||
        backtrack(board, row, col + 1, word, idx + 1))
        return true;
    //回溯。走不通時該位置還原爲本來的字母
    board[row][col] = word[idx];
    //cout<<board;
    //沒有分支返回true。程序返回false
    return false;
}
bool exist(vector<vector<char>> &board, string word)
{
    if (board.empty() || board[0].empty())
        return word.empty();
    for (int row = 0; row < board.size(); ++row)
    {
        for (int col = 0; col < board[0].size(); ++col)
        {
        //暴力搜索每一點作爲起點
            if (backtrack(board, row, col, word, 0))
                return true;
        }
    }
    return false;
};

劍13.機器人的運動範圍

地上有一個m行n列的方格,從座標 [0,0] 到座標 [m-1,n-1] 。一個機器人從座標 [0, 0] 的格子開始移動,它每次可以向左、右、上、下移動一格(不能移動到方格外),也不能進入行座標和列座標的數位之和大於k的格子。例如,當k爲18時,機器人能夠進入方格 [35, 37] ,因爲3+5+3+7=18。但它不能進入方格 [35, 38],因爲3+5+3+8=19。請問該機器人能夠到達多少個格子?

示例 1:

輸入:m = 2, n = 3, k = 1
輸出:3
示例 1:

輸入:m = 3, n = 1, k = 0
輸出:1

第一版代碼

根據上面一題直接寫出此題的代碼,提交結果爲超時,本地運行超時用例時等待很久,說明有死循環。本着“不放棄每一段代碼”的精神,我決定debug後再看書上的答案
在這裏插入圖片描述

class Solution {
public:
    int K;
    friend ostream &operator<<(ostream &out, const vector<vector<char>> &board)
    {
        for (auto i : board)
        {
            for (auto j : i)
            {
                out << j << " ";
            }
            out << endl;
        }
        out << endl;
        return out;
    }
    bool cant_move(int row, int col, int k)
    {
        int cnt = 0;
        while (row or col)
        {
            cnt += row % 10 + col % 10;
            row /= 10;
            col /= 10;
        }
        return cnt > k;
    }
    
    struct arrayHash{
    //避免哈希衝突
        int operator()(const array<int, 2> &p) const { return p[0] * 1e2 + p[1]; }
    };
    unordered_set<array<int, 2>,arrayHash> um;
    void backtrack(vector<vector<char>> &board, int row, int col)
    {
        //以下3個條件進行剪枝
        //1、數組越界終止條件
        if (row < 0 || row >= board.size() ||
            col < 0 || col >= board[0].size())
            return;
        //2、標誌位,防止重複遍歷
        if ('o' != board[row][col])
            return;
        //3、檢測該點是否符合題目條件
        if (cant_move(row, col, K))
            return;
        //設置標記位,如果後續回到此位置,比對字符即知
        board[row][col] = '*';
        //cout << board;
        array<int, 2> p;
        p[0] = row, p[1] = col;
        if(um.find(p)==um.end()){
            um.insert(p); //放入哈希表
        }
        
        //往四個方向走,
        backtrack(board, row - 1, col);
        backtrack(board, row + 1, col);
        backtrack(board, row, col - 1);
        backtrack(board, row, col + 1);
        //回溯。走不通時該位置還原爲本來的字母
        board[row][col] = 'o';
        //cout << board;
        //沒有分支返回true。程序返回false
        return;
    }
    int movingCount(int m, int n, int k) {
        vector<vector<char>> b(m,vector<char>(n,'o'));
        K=k;
        backtrack(b, 0, 0);
        return um.size();
    }
    
};

22. 括號生成

給出 n 代表生成括號的對數,請你寫出一個函數,使其能夠生成所有可能的並且有效的括號組合。
例如,給出 n = 3,生成結果爲:

[
“((()))”,
“(()())”,
“(())()”,
“()(())”,
“()()()”
]
分析:純剪枝,無回溯

class Solution {
    vector<string> ans;
    int N;
public:
    vector<string> generateParenthesis(int n) {
        N = n;
        DFS("",0,0);
        return ans;
    }
    void DFS(string s,int left,int right){
    	//兩個剪枝條件
        if(left < right) return;
        if(left > N) return;
        //終止遞歸條件
        if(s.length() == 2*N){
            ans.push_back(s);
            return;
        }
        //分支
        DFS(s+"(",left+1,right);
        DFS(s+")",left,right+1);
    }
};

51.N皇后

copy了份代碼,加點註釋分析下

class Solution {
public:
    vector<vector<string>> ans;
    vector<vector<string>> solveNQueens(int n) {
        //這裏一定要寫n,給record初始化
    	vector<int> record(n);
    	//字符串s初始化爲n個點
        string s="";
        for(int i=0;i<n;i++){
            s+='.';
        }
        //棋盤初始化爲n個向量,每個都是n個點,即n*n棋盤
        vector<string> temp(n, s);
        helper(0, n, temp, record);
        return ans;
    }
    /*
    l表示行數,n爲題目中的n,temp爲棋盤,recode記錄?
    */
    void helper(int l, int n, vector<string>&temp, vector<int>& record){
    	//正常終止遞歸條件,即棋盤填滿了
        if(l==n){
            ans.push_back(temp);
            return;
        }
        //從左到右依次填入棋子
        for(int i=0;i<n;i++){
        	//從左到右依次填入皇后
            record[l]=i;
            //如果不能填入,什麼也不做,相當於剪枝
            //如果能填入皇后,則填入
            if(isok(record, l)){
                temp[l][i]='Q';
                //填入本行的皇后後,遞歸尋找下一行皇后的位置
                helper(l+1, n, temp, record);
                //回溯,即取消在該點填入皇后
                temp[l][i]='.';
            }
        }
    }
    //檢查皇后能否填入的算法,row是遞歸程序當前的行
    bool isok(vector<int>& record, int row){
        for(int i=0;i<row;i++){
            if(record[i]==record[row]||row-record[row]==i-record[i]||row+record[row]==i+record[i])return false;
        }
        return true;
    }
};

以n = 4不剪枝爲例,每次遞歸程序處理一行。例如(0,0)填入後,又有(1,0)、(1,1)、(1,2)、(1,3)四種填法,第三行又有4種填法。關鍵是填入皇后(1,0)後,遞歸到下一層,遞歸返回後又重新填入(1,1),實現了每一層的4個分支

以下是我的解法:

#include <bits/stdc++.h>
using namespace std;
int cnt = 0;
void outPut(const vector<vector<char>> &b)
{
    for (int i = 0; i < b.size(); i++)
    {
        for (int j = 0; j < b.size(); j++)
        {
            cout << b[i][j] << " ";
        }
        cout << endl;
    }
    cout << endl;
}
void mark(int x, int y, vector<vector<char>> &b)
{
    int n = b.size();
    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < n; j++)
        {
            b[x][j] = '+';
            b[i][y] = '+';
            if (i + j == x + y or i - j == x - y)
            {
                b[i][j] = '+';
            }
        }
    }
    b[x][y] = 'x';
    //outPut(b);
}
void dfs(int level, vector<vector<char>> &b)
{
    if (level == b.size())
    {
        outPut(b);
        cnt++;
        return;
    }
    for (int i = 0; i < b.size(); i++)
    {
        if (b[level][i] == '.')
        {
            vector<vector<char>> tmp = b;
            mark(level, i, b);
            dfs(level + 1, b);
            b = tmp;
            //outPut(b);
        }
    }
}
int main()
{
    int N;
    cin >> N;
    vector<vector<char>> b(N, vector<char>(N, '.')); //棋盤
    dfs(0, b);
    //cout << cnt << endl;
    system("pause");
}

36.有效數獨

在這裏插入圖片描述

class Solution
{
    set<char> st1;
    set<char> st2;
public:
    bool cheak(char c, set<char> &st)
    {
        if (c <= 57 && c >= 49)
        {
            if (st.find(c) != st.end())
                return 0;
            else
                st.insert(c);
        }
        return 1;
    }
    bool isValidSudoku(vector<vector<char>> &board)
    {
        for (int i = 0; i < 9; i++)
        {
            st1.clear();
            st2.clear();
            for (int j = 0; j < 9; j++)
            {
                if (!cheak(board[i][j], st1))
                    return 0;
                if (!cheak(board[j][i], st2))
                    return 0;
            }
        }
        for (int u = 0; u < 3; u++)
        {
            for (int v = 0; v < 3; v++)
            {
                st1.clear();
                for (int i = 3 * u; i < 3 * u + 3; i++)
                {
                    for (int j = 3 * v; j < 3 * v + 3; j++)
                    {
                        if (!cheak(board[i][j], st1))
                            return 0;
                    }
                }
            }
        }
        return 1;
    }
};

37.解數獨

class Solution {
public:
    bool solveSudoku(vector<vector<char>>& board) {
        for ( int i = 0 ;i < board.size();i++){
            for(int j=0;j<board[i].size();j++){
                if(board[i][j]=='.'){//開始填入
                    for(char c='1';c<='9';c++){
                        if(isValid(board,i,j,c)){
                            board[i][j]=c;//填入
                            if(solveSudoku(board)) return true;//解完
                            else board[i][j]='.';//下層返回錯,回溯
                        }
                    }
                    return false;//填完了1-9 都不行 就返回錯
                }
            }
        }
      return true;
    }
private:
    bool isValid(vector<vector<char>>& board,int row,int col,char c){
      for(int i=0;i<9;i++){
        if(board[i][col]==c) return false;
        if(board[row][i]==c) return false;
        if(board[3*(row/3)+i/3][3*(col/3)+i%3]==c) return false;
      }
      return true;
    }
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章