七、基础算法精讲:回溯

目录

  • 一、子集型回溯
    • 1.1 电话号码
    • 1.2 子集
    • 1.3 分割回文串
  • 二、组合型与剪枝
    • 2.1 组合
    • 2.2 组合总和 III
    • 2.3 括号生成
  • 三、排列型
    • 3.1 全排列
    • 3.2 N 皇后
    • 3.3 N 皇后 II

一、子集型回溯

1.1 电话号码

Leetcode 17

MAPPING = "", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"

class Solution:
    def letterCombinations(self, digits: str) -> List[str]:
        n = len(digits)
        if n == 0:
            return []
        ans = []
        path = [''] * n
        def dfs(i: int)->None:
            if i == n:
                ans.append(''.join(path))
                return
            for c in MAPPING[int(digits[i])]:
                path[i] = c
                dfs(i + 1)
        dfs(0)
        return ans
class Solution {
private:
    string MAPPING[10] = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};

public:
    vector<string> letterCombinations(string digits) {
        int n = digits.size();
        if (!n) return {};
        vector<string> ans;
        string path(n, 0);
        function<void(int)> dfs = [&](int i ) {
            if (i == n) {
                ans.emplace_back(path);
                return;
            }
            for (char c: MAPPING[digits[i] - '0']) {
                path[i] = c;
                dfs(i + 1);
            }
        };
        dfs(0);
        return ans;
    }
};
  • 时间复杂度: O ( n 4 n ) O(n4^n) O(n4n)
  • 空间复杂度: O ( n ) O(n) O(n)

1.2 子集

Leetcode 78

解法一:选或者不选

class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        ans = []
        path = []
        n = len(nums)
        def dfs(i: int)->None:
            if i == n:
                ans.append(path.copy())
                return
            dfs(i + 1)
            path.append(nums[i])
            dfs(i + 1)
            path.pop()
        dfs(0)
        return ans
class Solution {
public:
    vector<vector<int>> subsets(vector<int>& nums) {
        vector<vector<int>> ans;
        vector<int> path;
        int n = nums.size();
        function<void(int)> dfs=[&](int i) {
            if (i == n) {
                ans.emplace_back(path);
                return;
            }
            // 不选
            dfs(i + 1);  
            
            // 选择
            path.push_back(nums[i]);
            dfs(i + 1); 
            path.pop_back();
        };
        dfs(0);
        return ans;
    }
};

解法二:选择哪个数

class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        ans, path = [], []
        n = len(nums)
        def dfs(i: int)->None:
            ans.append(path.copy())
            if i == n:
                return
            for j in range(i, n):
                path.append(nums[j])
                dfs(j + 1)
                path.pop()
        dfs(0)
        return ans
class Solution {
public:
    vector<vector<int>> subsets(vector<int>& nums) {
        vector<vector<int>> ans;
        vector<int> path;
        int n = nums.size();
        function<void(int)> dfs = [&] (int i) {
            ans.emplace_back(path);
            if (i == n) return;
            for (int j = i; j < n; j ++ ) {
                path.push_back(nums[j]);
                dfs(j + 1);
                path.pop_back();
            }
        };
        dfs(0);
        return ans;
    }
};

1.3 分割回文串

Leetcode 131

class Solution:
    def partition(self, s: str) -> List[List[str]]:
        ans, path = [], []
        n = len(s)
        def dfs(i: int) -> None:
            if i == n:
                ans.append(path.copy())
                return
            for j in range(i, n):
                t = s[i : j + 1]
                if t == t[::-1]:
                    path.append(t)
                    dfs(j + 1)
                    path.pop()
        dfs(0)
        return ans
class Solution {
    bool isPalindrome(string &s, int left, int right) {
        while (left < right)
            if (s[left ++ ] != s[right -- ])
                return false;
        return true;
    }

public:
    vector<vector<string>> partition(string s) {
        vector<vector<string>> ans;
        vector<string> path;
        int n = s.length();
        function<void(int)> dfs = [&](int i) {
            if (i == n) {
                ans.emplace_back(path);
                return;
            }
            for (int j = i; j < n; j ++ ) 
                if (isPalindrome(s, i, j)) {
                    path.push_back(s.substr(i, j - i + 1));
                    dfs(j + 1);
                    path.pop_back();
                }
        };
        dfs(0);
        return ans;
    }
};

二、组合型与剪枝

2.1 组合

Leetcode 77

解法一:选或者不选

class Solution:
    def combine(self, n: int, k: int) -> List[List[int]]:
        ans, path = [], []
        def dfs(i: int)->None:
            d = k -len(path)
            if d == 0:
                ans.append(path.copy())
                return
            
            # 不选i
            if i > d: dfs(i - 1)
            
			# 选择i
			path.append(i)
            dfs(i - 1)
            path.pop()
        dfs(n)
        return ans
class Solution {
public:
    vector<vector<int>> combine(int n, int k) {
        vector<vector<int>> ans;
        vector<int> path;
        function<void(int)> dfs = [&](int i) {
            int d = k - path.size();
            if (!d) {
                ans.emplace_back(path);
                return;
            }
            if (i > d) dfs(i - 1);
            path.push_back(i);
            dfs(i - 1);
            path.pop_back();
        };
        dfs(n);
        return ans;
    }
};

解法二:枚举下一个数选择哪个

class Solution:
    def combine(self, n: int, k: int) -> List[List[int]]:
        ans, path = [], []
        def dfs(i: int)->None:
            d = k - len(path)  # 还有选择d个数
            if d == 0:
                ans.append(path.copy())
                return
            for j in range(i, d - 1, -1):
                path.append(j)
                dfs(j - 1)
                path.pop()
        dfs(n)
        return ans
class Solution {
public:
    vector<vector<int>> combine(int n, int k) {
        vector<vector<int>> ans;
        vector<int> path;
        function<void(int)> dfs = [&](int i) {
            int d = k - path.size();
            if (!d) {
                ans.emplace_back(path);
                return;
            }
            for (int j = i; j >= d; j -- ) {
                path.push_back(j);
                dfs(j - 1);
                path.pop_back();
            }
        };
        dfs(n);
        return ans;
    }
};

2.2 组合总和 III

Leetcode 216

解法一:枚举下一个属选择哪个

class Solution:
    def combinationSum3(self, k: int, n: int) -> List[List[int]]:
        ans, path = [], []
        def dfs(i: int, t: int)->None:
            """
            i 表示枚举到数(1-9)
            t 表示剩余的总和
            """
            d = k - len(path)

            # 剪枝1:剩余总和小于0
            # 剪枝2:等差求和任然小于t
            if t < 0 or t > (i + i - d + 1) * d // 2:
                return

            if d == 0:
                ans.append(path.copy())
                return
            for j in range(i, d - 1, -1):
                path.append(j)
                dfs(j - 1, t - j)
                path.pop()
        dfs(9, n)
        return ans
class Solution {
public:
    vector<vector<int>> combinationSum3(int k, int n) {
        vector<vector<int>> ans;
        vector<int> path;
        function<void(int, int)> dfs = [&](int i, int t) {
            int d = k - path.size();
            if (t < 0 || t > (i + i - d + 1) * d / 2) return;
            if (!d) {
                ans.emplace_back(path);
                return;
            }
            for (int j = i; j >= d; j -- ) {
                path.push_back(j);
                dfs(j - 1, t - j);
                path.pop_back();
            }
        };
        dfs(9, n);
        return ans;
    }
};

解法二:选或者不选

class Solution:
    def combinationSum3(self, k: int, n: int) -> List[List[int]]:
        ans, path = [], []
        def dfs(i: int, t: int)->None:
            d = k - len(path)
            if t < 0 or t > (i + i - d + 1) * d // 2:
                return
            if d == 0:
                ans.append(path.copy())
                return

            if (i > d): 
                dfs(i - 1, t)
            
            path.append(i)
            dfs(i - 1, t - i)
            path.pop()
        dfs(9, n)
        return ans
class Solution {
public:
    vector<vector<int>> combinationSum3(int k, int n) {
        vector<vector<int>> ans;
        vector<int> path;
        function<void(int, int)> dfs = [&] (int i, int t) {
            int d = k - path.size();
            if (t < 0 || t > (i + i - d + 1) * d / 2) return;
            if (!d) {
                ans.emplace_back(path);
                return;
            }
            
            if (i > d) dfs(i - 1, t);

            path.push_back(i);
            dfs(i - 1, t - i);
            path.pop_back();
        };
        dfs(9, n);
        return ans;
    }
};

2.3 括号生成

Leetcode 22

解法一:枚举填左括号还是右括号

class Solution:
    def generateParenthesis(self, n: int) -> List[str]:
        m = n * 2
        ans = []
        path = [''] * m
        def dfs(i: int, open: int)->None:
            """
            i: 记录当前枚举到的位置
            open: 记录左括号个数
            """
            if i == m:
                ans.append(''.join(path))
                return
            if open < n:  # 还可以填写左括号
                path[i] = '('
                dfs(i + 1, open + 1)
            if i - open < open:  # 如果括号匹配, i-open == open
                path[i] = ')'
                dfs(i + 1, open)
        dfs(0, 0)
        return ans
class Solution {
public:
    vector<string> generateParenthesis(int n) {
        int m = n * 2;
        vector<string> ans;
        string path(m, 0);
        function<void(int, int)> dfs = [&](int i, int open) {
            if (i == m) {
                ans.emplace_back(path);
                return;
            }
            if (open < n) {
                path[i] = '(';
                dfs(i + 1, open + 1);
            }
            if (i - open < open) {
                path[i] = ')';
                dfs(i + 1, open);
            }
        };
        dfs(0, 0);
        return ans;
    }
};

解法二:枚举下一个左括号的位置

class Solution:
    def generateParenthesis(self, n: int) -> List[str]:
        ans, path = [], []

        # balance = 左括号个数 - 右括号个数
        def dfs(i: int, balance: int)->None:
            if len(path) == n:
                s = [')'] * (n * 2)
                for j in path:
                    s[j] = '('
                ans.append(''.join(s))
                return 
            
            # 可以填写 0 到 balance 个右括号
            for close in range(balance + 1):  # 填 close 个右括号
                path.append(i + close)  # 填 1 个左括号
                dfs(i + close + 1, balance - close + 1)
                path.pop()

        dfs(0, 0)
        return ans
class Solution {
public:
    vector<string> generateParenthesis(int n) {
        vector<string> ans;
        vector<int> path;

        function<void(int, int)> dfs=[&](int i, int balance) {
            if (path.size() == n) {
                string s(n * 2, ')');
                for (int j: path) s[j] = '(';
                ans.emplace_back(s);
                return;
            }

            for (int close = 0; close <= balance; close ++ ) {
                path.push_back(i + close);
                dfs(i + close + 1, balance - close + 1);
                path.pop_back();
            }
        };
        dfs(0, 0);
        return ans;
    }
};

三、排列型

3.1 全排列

Leetcode 46

class Solution:
    def permute(self, nums: List[int]) -> List[List[int]]:
        n = len(nums)
        ans, path = [], [0] * n
        on_path = [False] * n  # 判断是否该数是否被使用
        
        def dfs(i: int)->None:
            if i == n:
                ans.append(path.copy())
                return

            for j, on in enumerate(on_path):
                if not on:
                    path[i] = nums[j]
                    on_path[j] = True
                    dfs(i + 1)
                    on_path[j] = False
        
        dfs(0)
        return ans
class Solution {
public:
    vector<vector<int>> permute(vector<int>& nums) {
        int n = nums.size();
        vector<vector<int>> ans;
        vector<int> path(n), on_path(n);
        function<void(int)> dfs = [&](int i) {
            if (i == n) {
                ans.emplace_back(path);
                return;
            }
            for (int j = 0; j < n; j ++ ) 
                if (!on_path[j]) {
                    path[i] = nums[j];
                    on_path[j] = true;
                    dfs(i + 1);
                    on_path[j] = false;
                }
        };
        dfs(0);
        return ans;
    }
};

3.2 N 皇后

Leetcode 51

class Solution:
    def solveNQueens(self, n: int) -> List[List[str]]:
        m = n * 2 - 1
        ans = []
        col = [0] * n
        on_path, diag1, diag2 = [False] * n, [False] * m, [False] * m

        def dfs(r: int)->None:
            if r == n:
                ans.append(['.' * c + 'Q' + '.' * (n - 1 - c) for c in col])
                return
            for c, on in enumerate(on_path):
                if not on and not diag1[r + c] and not diag2[r - c]:
                    col[r] = c
                    on_path[c] = diag1[r + c] = diag2[r - c] = True
                    dfs(r + 1)
                    on_path[c] = diag1[r + c] = diag2[r - c] = False

        dfs(0)
        return ans
class Solution {
public:
    vector<vector<string>> solveNQueens(int n) {
        vector<vector<string>> ans;
        vector<int> col(n), on_path(n), diag1(n * 2 - 1), diag2(n * 2 - 1);

        function<void(int)> dfs=[&](int r) {
            if (r == n) {
                vector<string> board(n);
                for (int i = 0; i < n; i ++ ) 
                    board[i] = string(col[i], '.') + 'Q' + string(n - 1 - col[i], '.');
                ans.emplace_back(board);
                return;
            }

            for (int c = 0; c < n; c ++ ) {
                int rc = r - c + n - 1;
                if (!on_path[c] && !diag1[r + c] && !diag2[rc]) {
                    col[r] = c;
                    on_path[c] = diag1[r + c] = diag2[rc] = true;
                    dfs(r + 1);
                    on_path[c] = diag1[r + c] = diag2[rc] = false;
                }
            }
        };
        dfs(0);
        return ans;
    }
};

3.3 N 皇后 II

Leetcode 52

class Solution:
    def totalNQueens(self, n: int) -> int:
        m = n * 2 - 1
        ans = 0
        on_path, diag1, diag2 = [False] * n, [False] * m, [False] * m
        
        def dfs(r: int)->None:
            if r == n:
                nonlocal ans
                ans += 1
                return
            for c, on in enumerate(on_path):
                if not on and not diag1[r + c] and not diag2[r - c]:
                    on_path[c] = diag1[r + c] = diag2[r - c] = True
                    dfs(r + 1)
                    on_path[c] = diag1[r + c] = diag2[r - c] = False

        dfs(0)
        return ans
class Solution {
public:
    int totalNQueens(int n) {
        int ans = 0;
        vector<int> on_path(n), diag1(n * 2 - 1), diag2(n * 2 - 1);
        function<void(int)> dfs=[&] (int r) {
            if (r == n) {
                ans ++ ;
                return;
            }
            for (int c = 0; c < n; c ++ ) {
                int rc = r - c + n - 1;
                if (!on_path[c] && !diag1[r + c] && !diag2[rc]) {
                    on_path[c] = diag1[r + c] = diag2[rc] = true;
                    dfs(r + 1);
                    on_path[c] = diag1[r + c] = diag2[rc] = false;
                }
            }
        };
        dfs(0);
        return ans;
    }
};

你可能感兴趣的:(算法笔记,2,算法,深度优先)