回溯問題

一、概念

參考文章
回溯算法實際上一個類似枚舉的搜索嘗試過程,主要是在搜索嘗試過程中尋找問題的解,當發(fā)現(xiàn)已不滿足求解條件時,就“回溯”返回,嘗試別的路徑。

回溯法是一種選優(yōu)搜索法,按選優(yōu)條件向前搜索,以達到目標。但當探索到某一步時,發(fā)現(xiàn)原先選擇并不優(yōu)或達不到目標,就退回一步重新選擇,這種走不通就退回再走的技術(shù)為回溯法,而滿足回溯條件的某個狀態(tài)的點稱為“回溯點”。

許多復雜的,規(guī)模較大的問題都可以使用回溯法,有“通用解題方法”的美稱。

二、基本思想

在包含問題的所有解的解空間樹中,按照深度優(yōu)先搜索的策略,從根結(jié)點出發(fā)深度探索解空間樹。當探索到某一結(jié)點時,要先判斷該結(jié)點是否包含問題的解,如果包含,就從該結(jié)點出發(fā)繼續(xù)探索下去,如果該結(jié)點不包含問題的解,則逐層向其祖先結(jié)點回溯。(其實回溯法就是對隱式圖的深度優(yōu)先搜索算法)。

若用回溯法求問題的所有解時,要回溯到根,且根結(jié)點的所有可行的子樹都要已被搜索遍才結(jié)束。

而若使用回溯法求任一個解時,只要搜索到問題的一個解就可以結(jié)束。

三、用回溯法解題的一般步驟:

  1. 針對所給問題,確定問題的解空間:首先應(yīng)明確定義問題的解空間,問題的解空間應(yīng)至少包含問題的一個(最優(yōu))解。
  2. 確定結(jié)點的擴展搜索規(guī)則
  3. 以深度優(yōu)先方式搜索解空間,并在搜索過程中用剪枝函數(shù)避免無效搜索。

四、算法模板

/**
* dfs模板.
* @param[in] input 輸入數(shù)據(jù)指針
* @param[out] path 當前路徑,即中間結(jié)果
* @param[out] result 最終結(jié)果
* @param[inout] cur or gap 標記當前位置或距離目標的距離
*/
void dfs(type &input, type &path, type &result, int cur or gap) {
    if (數(shù)據(jù)非法) return 0; // 終止條件
    if (滿足條件) {
        將path 放入result
    }
    if (可以剪枝) return;
    for(...) { // 執(zhí)行所有可能的擴展動作
        執(zhí)行動作,修改path
        dfs(input, path, result, cur + 1 or gap--, );
        恢復path //向前回溯
    }
}

五、檢測一下是不是真的會了呢

1.leetcode401-二進制手表

題目描述

二進制手表頂部有 4 個 LED 代表小時(0-11),底部的 6 個 LED 代表分鐘(0-59)。每個 LED 代表一個 0 或 1,最低位在右側(cè)。

例如,上面的二進制手表讀取 “3:25”。給定一個非負整數(shù) n 代表當前 LED 亮著的數(shù)量,返回所有可能的時間。

輸入: n = 1
返回: ["1:00", "2:00", "4:00", "8:00", "0:01", "0:02", "0:04", "0:08", "0:16", "0:32"]

回溯法

這個題目可以歸于有多少 n個1的二進制組合。轉(zhuǎn)換為字符串即可。 這里將 0 - 9,劃分一下 0 - 3 是 小時, 6 - 9 是分鐘計算。

  • 結(jié)束條件: num==0且h,m合法
  • 退出條件: h或m非法

class Solution {
public:
    void readBinaryWatch(vector<string> &res, int h, int m, int num, int cur){
        if(num==0 && h>=0 && m>=0){
            string s = to_string(h) + (m<10 ? ":0" : ":") + to_string(m);
            res.push_back(s);
        }
        for(int i=cur;i<10;i++){
            if(i<=3){
                h += pow(2, i);
                if(h>11){
                    h -= pow(2, i);
                    continue;
                }
            }else{
                m += pow(2, i-4);
                if(m>59) return;
            }

            readBinaryWatch(res, h, m, num-1, i+1);
            if(i<=3) h -= pow(2, i);
            else m -= pow(2, i-4);
        }
    }
    vector<string> readBinaryWatch(int num) {
        vector<string> res;
        if(num<0 || num>8) return res;
        readBinaryWatch(res, 0, 0, num, 0);
        return res;
    }
};

bitset法

class Solution {
public:
    vector<string> readBinaryWatch(int num) {//bitset STL模板
        vector<string> times;
        for (int i = 0; i < 12; i++) {
            bitset<4> h(i);//4位的二進制數(shù)
            for (int j = 0; j < 60; j++) {
                bitset<6> m(j);//6位的二進制數(shù)
                if (h.count() + m.count() == num)//h.count()函數(shù)判斷h中1的個數(shù)
                    times.push_back(to_string(i) + (j < 10? ":0": ":") + to_string(j));
            }
        }
        return times;
    }
};

2.leetcode39-組合總數(shù)

題目描述

給定一個無重復元素的數(shù)組 candidates 和一個目標數(shù) target ,找出 candidates 中所有可以使數(shù)字和為 target 的組合。candidates 中的數(shù)字可以無限制重復被選取。

說明:
所有數(shù)字(包括 target)都是正整數(shù)。解集不能包含重復的組合。

示例 :
輸入: candidates = [2,3,6,7], target = 7,所求解集為:
[[7],
[2,2,3]]

回溯法

解空間是任意選取的任意個數(shù)字組成的數(shù)組.遍歷數(shù)組中的值,如果nums[i] < target , 嘗試把nums[i]作為一個加數(shù),把目標值減去nums[i],下一次遞歸從i+1開始遍歷數(shù)組尋找下一個加數(shù);如果target=0,說明找到了一組加數(shù);否則把上一個加數(shù)從list中去掉.

  • 結(jié)束條件: target==0
  • 退出條件: target<0

class Solution {
public:
    void dfs(vector<vector<int>> &res, vector<int> &temp, vector<int> candidates, int target, int index){
        if(target<0) return ;
        if(target==0){
            res.push_back(temp);
        }
        for(int i=index; i < candidates.size(); i++){
            target -= candidates[i];
            temp.push_back(candidates[i]);
            dfs(res, temp, candidates, target, i);
            target += candidates[i];
            temp.pop_back();
        }
    }

    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        vector<vector<int>> res;
        vector<int> temp;
        dfs(res, temp, candidates, target, 0);
        return res;
    }
};

3.leetcode40-組合總數(shù) II

題目描述

給定一個數(shù)組 candidates 和一個目標數(shù) target ,找出 candidates 中所有可以使數(shù)字和為 target 的組合。candidates 中的每個數(shù)字在每個組合中只能使用一次。
說明:所有數(shù)字(包括目標數(shù))都是正整數(shù)。解集不能包含重復的組合。

示例:
輸入: candidates = [10,1,2,7,6,1,5], target = 8,所求解集為:
[[1, 7],
[1, 2, 5],
[2, 6],
[1, 1, 6]]

回溯法

和上一題基本相同,唯一需要注意的是每個數(shù)字只許使用一次且不能有重復組合.
可先對數(shù)組排序,保留深度方向上相同的數(shù)字(也就是多個重復數(shù)字時可用,比如[1,1,1],第一個‘1’使用過后,第二和第三個依然可以使用),剔除水平方向相同的(也就是同一層中相同的枝應(yīng)該剪掉)。

  • 結(jié)束條件: target==0
  • 退出條件: target<0

代碼

class Solution {
public:
    void dfs(vector<vector<int>> &res, vector<int> &temp, vector<int> candidates, int target, int index){
        if(target<0) return;
        if(target==0){
            res.push_back(temp);
        }
        for(int i=index; i<candidates.size()&&candidates[i]<=target;i++){
            if(i>index&&candidates[i-1]==candidates[i]) continue;
            target -= candidates[i];
            temp.push_back(candidates[i]);
            dfs(res, temp, candidates, target, i+1);
            target += candidates[i];
            temp.pop_back();
        }
    }

    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        vector<vector<int>> res;
        vector<int> temp;

        if(candidates.size()==0) return res;
        sort(candidates.begin(), candidates.end());
        dfs(res, temp, candidates, target, 0);
        return res;
    }
};

4.leetcode46-全排列

題目描述

給定一個沒有重復數(shù)字的序列,返回其所有可能的全排列。

示例:
輸入: [1,2,3]
輸出:
[[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]]

回溯法

  • 對現(xiàn)有序列 x 進行遍歷,拿到每一個遍歷值放在當前位上
  • 將該遍歷到的值抽離序列 x,生成一個新的序列 y
  • 繼續(xù)對序列 y 執(zhí)行這一過程

代碼

class Solution {
public:
    void dfs(vector<vector<int>> &res, vector<int> &nums, int left, int right){
        if(left==right){
            res.push_back(nums);
        }
        for(int i=left;i<=right;i++){
            swap(nums[i], nums[left]);
            dfs(res, nums, left+1, right);
            swap(nums[i], nums[left]);
        }
    }

    vector<vector<int>> permute(vector<int>& nums) {
        vector<vector<int>> res;
        dfs(res, nums, 0, nums.size()-1);
        return res;
    }
};

5.leetcode47-全排列II

題目描述

給定一個可包含重復數(shù)字的序列,返回所有不重復的全排列。

示例:
輸入: [1,1,2]
輸出:
[[1,1,2],
[1,2,1],
[2,1,1]]

回溯法

這篇寫的很詳細

代碼一

class Solution {
public:
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        vector<vector<int>> res;
        sort(nums.begin(), nums.end());
        permute(nums, 0, res);
        return res;
    }
    void permute(vector<int> nums, int start, vector<vector<int>>& res) {
        if (start >= nums.size()) res.push_back(nums);
        for (int i = start; i < nums.size(); ++i) {
            if (i != start && nums[i] == nums[start]) continue;
            swap(nums[i], nums[start]);
            permute(nums, start + 1, res);
        }
    }
};

代碼二

class Solution {
public:
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        for (int v : nums) um[v]++;
        vector<int> perm;
        helper(perm, nums.size());
        return ret;
    }

    void helper(vector<int> &perm, int num) {
        if (perm.size() == num) {
        ret.push_back(perm);
        return;
    }

    for (auto &it : um) {
        if (it.second > 0) {
            it.second--;
            perm.push_back(it.first);
            helper(perm, num);
            perm.pop_back();
            it.second++;
        }
    }
}

private:
    unordered_map<int, int> um;
    vector<vector<int>> ret;
};

6.leetcode78-子集

題目描述

給定一組不含重復元素的整數(shù)數(shù)組 nums,返回該數(shù)組所有可能的子集(冪集)。
說明:解集不能包含重復的子集。

示例:
輸入: nums = [1,2,3]
輸出:
[ [3],
[1],
[2],
[1,2,3],
[1,3],
[2,3],
[1,2],
[]]

回溯法

添加一個數(shù),遞歸,刪除之前的數(shù),下次循環(huán)。

代碼

class Solution {
public:
    void dfs(vector<vector<int>> &res, vector<int> &temp, vector<int> nums, int index){
        res.push_back(temp);
        for(int i=index; i<nums.size();i++){
            temp.push_back(nums[i]);
            dfs(res, temp, nums, i+1);
            temp.pop_back();
        }
    }

    vector<vector<int>> subsets(vector<int>& nums) {
        vector<vector<int>> res;
        vector<int> temp;
        dfs(res, temp, nums, 0);
        return res;
    }
};

7.leetcode51-N皇后

題目描述

n 皇后問題研究的是如何將 n 個皇后放置在 n×n 的棋盤上,并且使皇后彼此之間不能相互攻擊。



上圖為 8 皇后問題的一種解法。給定一個整數(shù) n,返回所有不同的 n 皇后問題的解決方案。每一種解法包含一個明確的 n 皇后問題的棋子放置方案,該方案中 'Q' 和 '.' 分別代表了皇后和空位。

示例:
輸入: 4
輸出: [
[".Q..", // 解法 1
"...Q",
"Q...",
"..Q."],

["..Q.", // 解法 2
"Q...",
"...Q",
".Q.."]]
解釋: 4 皇后問題存在兩個不同的解法。

回溯法

用set記錄 列, 正對角,負對角是否已經(jīng)擺放過皇后,如果是則跳過??!

如何判斷是否在對角上呢?

  • 正對角就是相加之和一樣的
  • 負對角就是相減只差一樣的

代碼

class Solution {
public:
    void dfs(int row, vector<vector<string>> &res, vector<string> &temp,
    unordered_set<int> &cols, unordered_set<int> &hills, unordered_set<int> dales, int n){
        if(row==n){
            res.push_back(temp);
            return ;
        }
        for(int col=0;col<n;col++){
            if(cols.count(col)>0 || hills.count(col+row)>0 || dales.count(row-col)>0) continue;
            cols.insert(col);
            hills.insert(col+row);
            dales.insert(row-col);
            temp[row][col] = 'Q';
            dfs(row+1, res, temp, cols, hills, dales, n);
            cols.erase(col);
            hills.erase(col+row);
            dales.erase(row-col);
            temp[row][col] = '.';
        }
    }

    vector<vector<string>> solveNQueens(int n) {
        vector<vector<string>> res;
        vector<string> temp(n, string(n, '.'));
        unordered_set<int> cols;
        unordered_set<int> hills;
        unordered_set<int> dales;
        dfs(0, res, temp, cols, hills, dales, n);
        return res;
    }
};

8.leetcode52-N皇后II

題目描述

n 皇后問題研究的是如何將 n 個皇后放置在 n×n 的棋盤上,并且使皇后彼此之間不能相互攻擊。



上圖為 8 皇后問題的一種解法。給定一個整數(shù) n,返回 n 皇后不同的解決方案的數(shù)量。

示例:
輸入: 4
輸出: 2
解釋: 4 皇后問題存在如下兩個不同的解法。
[[".Q..", // 解法 1
"...Q",
"Q...",
"..Q."],

["..Q.", // 解法 2
"Q...",
"...Q",
".Q.."]]

回溯法

和上一題一樣,這里如果滿足情況加1即可,比上題簡單一些!

代碼

class Solution {
public:
    void dfs(unordered_set<int> &cols, unordered_set<int> &hills, unordered_set<int> &dales, int &res, int row, int n){
        if(row==n){
            res++;
            return ;
        }
        for(int i=0;i<n;i++){
            if(cols.count(i)>0 || hills.count(i+row)>0 || dales.count(row-i)>0) continue;
            cols.insert(i);
            hills.insert(i+row);
            dales.insert(row-i);
            dfs(cols, hills, dales, res, row+1, n);
            cols.erase(i);
            hills.erase(i+row);
            dales.erase(row-i);
        }
    }

    int totalNQueens(int n) {
        unordered_set<int> cols;
        unordered_set<int> hills;
        unordered_set<int> dales;
        int res=0;

        dfs(cols, hills, dales, res, 0, n);
        return res;
    }
};
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容