9. 回溯


套路很深,就是遍历全部情况
  1. 定义解空间,包含全部解
  2. 利用深度优先搜索解空间
  3. Trial,减枝。(避免访问不可能产生解的子空间)
根据条件有选择的遍历,叫做剪枝或分枝定界。
主要分为2中情况:
  1. 宽度不定,解空间大小通常为2^m(排列问题的解空间是n!阶乘形)。该类题目通常为,cur的长度不定的题目。如39、40、78等。

总规模为 1 + m + (m-1+m-2+...+1) + (m-2+m-3+...+1) + ... + 1 = 2^m
每层节点个数不一样,越来越少。

void helper(输入,int depth,vector> &res, vector &cur) {
    if (满足条件) { 
        res.push_back(cur);
        return;
    }
    for (auto i = depth; i < m; ++i) { 
        //3. 减枝条件
        if (满足条件) { 
            cur.push_back(xxx);
            helper(输入,i+1,结束条件更新,res, cur);
            cur.pop_back();
        }
    }
}
  1. 宽度固定。

每次递归的规模都是一样的,所以总规模m+m2+m3+...+m^k。此时宽度为m,深度为k。最后一层规模为m^k,该层结果加入结果集中。
指数级增长,1+m+m2+m3+...+m^k

void helper(输入,int depth,结束条件, vector> &res, vector &cur) {
    // 1. DFS到达解空间底部
    if (depth == 解空间深度) { 
        res.push_back(cur);
        return;
    }
    // 2. 宽度
    for (auto i = 0; i < m; ++i) { 
        //3. 减枝条件
        if (满足条件) { 
            cur.push_back(xxx);
            helper(输入,depth + 1,结束条件更新,res, cur);
            cur.pop_back();
        }
    }
}

39. Combination Sum
Given a set of candidate numbers (C) (without duplicates) and a target number (T), find all unique combinations in C where the candidate numbers sums to T.
The same repeated number may be chosen from C unlimited number of times.
Note:
All numbers (including target) will be positive integers.
The solution set must not contain duplicate combinations.
For example, given candidate set [2, 3, 6, 7] and target 7, 
A solution set is: 
[
  [7],
  [2, 2, 3]
]
候选集合中选取数字,其和为目标值,同一元素可以重复出现。
解空间: 深度不定, 宽度为candidates.size() - lastIndex。
减枝: 和小于等于target
vector> combinationSum(vector& candidates, int target) {
    vector> res;
    vector cur;
    helper(candidates, target, 0, res, cur);
    return res;
}
void helper(vector& candidates, int target, int index, vector> &res, vector &cur) {
    if (!target) {
        res.push_back(cur);
        return;
    } 
    
    for (int i = index; i < candidates.size(); ++i) {
        if (candidates[i] <= target) {
            cur.push_back(candidates[i]);
            helper(candidates, target - candidates[i], i, res, cur);
            cur.pop_back();
        }
    }
}

40. Combination Sum II
Given a collection of candidate numbers (C) and a target number (T), find all unique combinations in C where the candidate numbers sums to T.
Each number in C may only be used once in the combination.
Note:
All numbers (including target) will be positive integers.
The solution set must not contain duplicate combinations.
For example, given candidate set [10, 1, 2, 7, 6, 1, 5] and target 8, 
A solution set is: 
[
  [1, 7],
  [1, 2, 5],
  [2, 6],
  [1, 1, 6]
]
候选集合中选取数字,其和为目标值,同一元素不可重复出现。不可重复,所以递归函数调用下一个,同时完成递归后将指针移动到与当前元素值不同的位置。
解空间:2^m。深度最大为m, 宽度为candidates.size() - lastIndex - 1。
减枝条件: 和小于等于target。
vector> combinationSum2(vector& candidates, int target) {
    std::sort(candidates.begin(), candidates.end());
    vector> res;
    vector cur;
    helper(candidates, target, 0, res, cur);
    return res;
}
void helper(vector &candidates, int target, int index, vector> &res, vector &cur) {
    if (target == 0) {
        res.push_back(cur);
        return;
    }
    
    for (int i = index; i < candidates.size(); ++i) {
        if (candidates[i] <= target) {
            cur.push_back(candidates[i]);
            helper(candidates, target - candidates[i], i + 1, res, cur);
            cur.pop_back();
        }
        while (i + 1 < candidates.size() && candidates[i] == candidates[i+1]) ++i;
    }
}

216. Combination Sum III
Find all possible combinations of k numbers that add up to a number n, given that only numbers from 1 to 9 can be used and each combination should be a unique set of numbers.
从1-9任取k次,和为n的情况。不可重复。
解空间:2^m。深度最大为m, 宽度为candidates.size() - lastIndex - 1。
减枝条件: 和小于等于target。
class curution {
public:
  vector> combinationSum3(int k, int n) {
    vector> result;
    vector cur;
    combination(result, cur, k, n);
    return result;
  }  
  
  void combination(vector>& result, vector cur, int k, int n) {
    if (cur.size() == k && n == 0) { result.push_back(cur); return ; }
    if (cur.size() < k) {
      for (int i = cur.empty() ? 1 : cur.back() + 1; i <= 9; ++i) {
        if (n - i < 0) break;
        cur.push_back(i);
        combination(result, cur, k, n - i);
        cur.pop_back();
      }
    }
  }
};

78. Subsets
Given a set of distinct integers, nums, return all possible subsets.
Note: The solution set must not contain duplicate subsets.
For example,
If nums = [1,2,3], a solution is:
[
  [3],
  [1],
  [2],
  [1,2,3],
  [1,3],
  [2,3],
  [1,2],
  []
]
求int数组的子集。
解空间:总大小2^m, m为子集大小。宽度不定型
减枝:无
class Solution {
public:
    vector> subsets(vector& nums) {
        sort(nums.begin(), nums.end());
        vector> res;
        vector cur;
        helper(nums, 0, res, cur);
        return res;
    }
    
    void helper(vector &nums, int depth, vector> &res, vector &cur) {
        res.push_back(cur);
        for (int i = depth; i < nums.size(); ++i) {
            cur.push_back(nums[i]);
            helper(nums, i+1, res, cur);
            cur.pop_back();
        }
    }
};
// 迭代
class Solution {
public:
    vector> subsets(vector& nums) {
        sort(nums.begin(), nums.end());
        vector> subs(1, vector());
        for (int i = 0; i < nums.size(); i++) {
            int n = subs.size();
            for (int j = 0; j < n; j++) {
                subs.push_back(subs[j]); 
                subs.back().push_back(nums[i]);
            }
        }
        return subs;
    }
}; 
// Bit Manipulation
class Solution {
public:
    vector> subsets(vector& nums) {
        sort(nums.begin(), nums.end());
        int num_subset = pow(2, nums.size()); 
        vector > res(num_subset, vector());
        for (int i = 0; i < nums.size(); i++)
            for (int j = 0; j < num_subset; j++)
                if ((j >> i) & 1)
                    res[j].push_back(nums[i]);
        return res;  
    }
};

90. Subsets II
Given a collection of integers that might contain duplicates, nums, return all possible subsets.
Note: The solution set must not contain duplicate subsets.
For example,
If nums = [1,2,2], a solution is:
[
  [2],
  [1],
  [1,2,2],
  [2,2],
  [1,2],
  []
]
求int数组的子集。
解空间:总大小2^m, m为子集大小。宽度不定型
减枝:i == depth || nums[i] != nums[i-1]
class Solution {
public:
    vector> subsetsWithDup(vector& nums) {
        std::sort(nums.begin(), nums.end());
        vector> res;
        vector cur;
        helper(nums, 0, res, cur);
        return res;
    }
    
    void helper(vector &nums, int depth, vector> &res, vector &cur) {
        res.push_back(cur);
        for (int i = depth; i != nums.size(); ++i) { 
            if (i == depth || nums[i] != nums[i-1]) {
                cur.push_back(nums[i]);
                helper(nums, i+1, res, cur);
                cur.pop_back();
            }
        }
    }
};

131. Palindrome Partitioning
Given a string s, partition s such that every substring of the partition is a palindrome.
Return all possible palindrome partitioning of s.
For example, given s = "aab",
Return
[
  ["aa","b"],
  ["a","a","b"]
]
求字符串分割为回文子串的全部情况。
  1. 迭代,遍历给定字符串从起始位置到终止,每个位置代表s.substr(index, i - index + 1)。若该子串为回文,则该子串加入cur中,并递归找出去除该子串之后位置的回文。
  2. 递归,递归函数结束条件为起始位置=字符串长度
解空间: 2^n, n为字符串长度
减枝:s[index, i]为回文
vector> partition(string s) {
    vector> res;
    vector cur;
    palindrome(s, 0, res, cur);
    return res;
}
void palindrome(string s, int index, vector> &res, vector &cur) {
    if (index == s.size()) {
        res.push_back(cur);
        return;
    }
    
    for (auto i = index; i < s.size(); ++i) {
        auto left = index, right = i;
        while (left < right && s[left] == s[right]) {
            left++;
            right--;
        }
        if (left>=right) {
            cur.push_back(s.substr(index, i - index + 1));
            palindrome(s, i + 1, res, cur);
            cur.pop_back();
        }
    }
}

46. Permutations
Given a collection of distinct numbers, return all possible permutations.

For example,
[1,2,3] have the following permutations:
[
  [1,2,3],
  [1,3,2],
  [2,1,3],
  [2,3,1],
  [3,1,2],
  [3,2,1]
]
求全排列n!。
解空间:n!
减枝:无
class Solution {
public:
    // 解空间:n!
    vector> permute(vector& nums) {
        vector> res;
        vector cur;
        queue input;
        for (int n : nums) input.push(n);
        helper(input, res, cur);
        return res;
    }

    void helper(queue &input,vector> &res, vector &cur) {
        if (input.size() == 0) {
            res.push_back(cur);
            return;
        }
        auto flag = input.size();
        for (auto i = 0; i != flag; ++i) {
            auto element = input.front();
            cur.push_back(element);
            input.pop();
            helper(input, res, cur);
            input.push(element);
            cur.pop_back();
        }
    }
};

47. Permutations II
Given a collection of numbers that might contain duplicates, return all possible unique permutations.

For example,
[1,1,2] have the following unique permutations:
[
  [1,1,2],
  [1,2,1],
  [2,1,1]
]
数组中存在重复元素,求数组的全排列。
解空间:n!
减枝:i==0 || (i > 0 && element != last)
class Solution {
public:
    vector> permuteUnique(vector& nums) {
        std::sort(nums.begin(), nums.end());
        vector> res;
        vector cur;
        queue input;
        for (auto n:nums) input.push(n);
        
        helper(input, res, cur);
        return res;
    }
    
    void helper(queue &input, vector> &res, vector &cur) {
        if (input.empty()) {
            res.push_back(cur);
            return;
        }
        
        auto flag = input.size();
        auto last = -1;
        for (auto i = 0; i < flag; ++i) {
            auto element = input.front();
            input.pop();
            if (i==0 || (i > 0 && element != last)) {
                cur.push_back(element);
                helper(input, res, cur);
                cur.pop_back();
            }
            input.push(element);
            last = element;
        }
    }
};

93. Restore IP Addresses
Given a string containing only digits, restore it by returning all possible valid IP address combinations.

For example:
Given "25525511135",

return ["255.255.11.135", "255.255.111.35"]. (Order does not matter)
求数字序列构成ip地址的可能情况。
解空间:3^m,m为字符串长度
减枝:tmpInt < 256 && to_string(tmpInt).size() == tmp.size() && divides < 4
class Solution {
public:
    vector restoreIpAddresses(string s) {
        vector res;
        string cur;
        helper(s, 0, res, cur);
        return res;
    }

    void helper(string s, int divides, vector &res, string &cur) {
        if (divides == 4 && s.size() == 0) {
            res.push_back(cur.substr(0, cur.size() - 1));
            return;
        }

        for (int i = 1; i <= 3 && i <= s.size(); ++i) {
            string tmp = s.substr(0, i);
            int tmpInt = stoi(tmp);
            if (tmpInt < 256 && to_string(tmpInt).size() == tmp.size() && divides < 4) {
                cur += (tmp + '\.');
                helper(s.substr(i), divides+1, res, cur);
                cur = cur.substr(0, cur.size() - i - 1);
            }
        }       
    }
};

79. Word Search
Given a 2D board and a word, find if the word exists in the grid.

The word can be constructed from letters of sequentially adjacent cell, where "adjacent" cells are those horizontally or vertically neighboring. The same letter cell may not be used more than once.

For example,
Given board =

[
  ['A','B','C','E'],
  ['S','F','C','S'],
  ['A','D','E','E']
]
word = "ABCCED", -> returns true,
word = "SEE", -> returns true,
word = "ABCB", -> returns false.
在字符矩阵中找某个字符串是否存在,可以水平或垂直移动,4个方向。
时间复杂度:O(n!)
解空间:mn(4^k), m*n为board大小, k为单词长度
DFS:迭代规模为m*n,递归深度为k。
减枝:4个方向的字符为当前字符
bool exist(vector>& board, string word) {
    for (int i = 0; i < board.size(); ++i) {
        for (int j = 0; j < board[0].size(); ++j) {
            if (search(board, i, j, word, 0)) return true;
        }
    }
    return false;
}

bool search(vector> &board, int i, int j, string &word, int begin) {
    if (i >= 0 && i < board.size() && j >= 0 && j < board[0].size() && board[i][j] == word[begin]) {
        if (begin == word.size() - 1) return true;
        
        char t = board[i][j];
        board[i][j] = '\0';
        if (search(board, i - 1, j, word, begin + 1)||search(board, i + 1, j, word, begin + 1)||search(board, i, j - 1, word, begin + 1)||search(board, i, j + 1, word, begin + 1))
            return true;
        board[i][j] = t;
        return false;
    }
    else {
        return false;
    }
}

77. Combinations
Given two integers n and k, return all possible combinations of k numbers out of 1 ... n.
For example,
If n = 4 and k = 2, a solution is:
[
  [2,4],
  [3,4],
  [2,3],
  [1,2],
  [1,3],
  [1,4],
]
返回给定的全部组合数目
解空间: n^k
减枝:前面的小于后面的。
vector> combine(int n, int k) {
    vector> res;
    vector cur;
    helper(n, 1, k, res, cur);
    return res;
}
void helper(int n, int index, int k, vector> &res, vector &cur) {
    if (k == 0) {
        res.push_back(cur);
        return;
    }
    
    for (int i = index; i <= n - k + 1; ++i) {
        cur.push_back(i);
        helper(n, i + 1, k - 1, res, cur);
        cur.pop_back();
    }
}

22. Generate Parentheses
Given n pairs of parentheses, write a function to generate all combinations of well-formed parentheses.
For example, given n = 3, a solution set is:
[
  "((()))",
  "(()())",
  "(())()",
  "()(())",
  "()()()"
]
生成合法括号序列。
解空间:2^n。n个位置,去左括号或右括号
减枝:1. 左括号个数小于n时,可以添加左括号。2. 左括号多于右括号时,可以添加右括号。
vector generateParenthesis(int n) {
    vector res;
    string cur;
    helper(n, n, res, cur);
    return res;
}
void helper(int left, int right, vector &res, string &cur) {
    if (!left && !right) {
        res.push_back(cur);
        return;
    }
    
    if (left > 0) {
        cur.push_back('(');
        helper(left - 1, right, res, cur);
        cur.pop_back();
    }
    
    if (left < right) {
        cur.push_back(')');
        helper(left, right - 1, res, cur);
        cur.pop_back();
    }
}

37. Sudoku Solver
Write a program to solve a Sudoku puzzle by filling the empty cells.
Empty cells are indicated by the character '.'.
You may assume that there will be only one unique solution.
数独求解,行、列、3*3box中都不能有重复值。
解空间:9^m。
减枝:尝试插入数值,满足行、列、3*3box中都不能有重复值。
class Solution {
public:
    void solveSudoku(vector>& board) {
        solve(board, 0);
    }
    
    bool solve(vector>& board, int cur) {      
        while (cur < 81 && board[cur / 9][cur%9] != '.') cur++; 
        if (cur == 81) return true;
        int i = cur / 9, j = cur%9;
        for (auto c = '1'; c <= '9'; ++c) {
            if (isValid(board, i, j, c)) {
                board[i][j] = c;
                if (solve(board, cur + 1))
                    return true;
                else
                    board[i][j] = '.';
            }
        }

        return false;
    }
    
    bool isValid(vector> &board, int i, int j, char c) {
        for (int k = 0; k < 9; ++k) {
            if (board[i][k] == c) return false;
            if (board[k][j] == c) return false;
            if (board[3*(i/3)+k/3][3*(j/3)+k%3] == c) return false;
        }
        return true;
    }
};

51. N-Queens
n皇后问题。
解空间:n^n。
减枝:皇后不能在同一行、同一列、45度角(行差=列差)。
class Solution {
public:
    vector> solveNQueens(int n) {
        vector> res;
        vector cur;
        vector occupied;
        helper(n, occupied, res, cur);
        cout << res.size() << endl;
        return res;
    }
    
    void helper(int n, vector &occupied, vector> &res, vector &cur) {
        if (occupied.size() == n) {
            res.push_back(cur);
            return;
        }
        
        for (int i = 0; i < n; i++) {
            string tmp(n, '.');
            if (isSafe(occupied, i, occupied.size())) {
                tmp[i] = 'Q';
                cur.push_back(tmp);
                occupied.push_back(i);
                helper(n, occupied, res, cur);
                cur.pop_back();
                occupied.pop_back();
            } 
        }
    }
    
    bool isSafe(vector &occupied, int trial, int loc) {
        for (auto i = 0; i < occupied.size(); ++i) {
            if (occupied[i] == trial || abs(trial - occupied[i]) == abs(loc - i))
                return false;
        }
        return true;
    }
};

52. N-Queens II
n皇后解的个数。
解空间:n^n。
减枝:皇后不能在同一行、同一列、45度角(行差=列差)。
class Solution {
public:
    int totalNQueens(int n) {
        int res = 0;
        vector occupied;
        helper(n, 0, occupied, res);
        return res;
    }
    
    void helper(int n, int depth, vector &occupied, int &res) {
        if (depth == n) {
            res++;
            return;
        }
        
        for (int i = 0; i < n; i++) {
            if (isSafe(occupied, i, occupied.size())) {
                occupied.push_back(i);
                helper(n, depth + 1, occupied, res);
                occupied.pop_back();
            } 
        }
    }
    
    bool isSafe(vector &occupied, int trial, int loc) {
        for (auto i = 0; i < occupied.size(); ++i) {
            if (occupied[i] == trial || abs(trial - occupied[i]) == abs(loc - i))
                return false;
        }
        return true;
    }
};

17. Letter Combinations of a Phone Number
9键键盘数字序列对应的所有字母序列的。
解空间: 4^n,n为数字序列的长度。
减枝: 无
vector letterCombinations(string digits) {
    static const vector digit2letter = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
    vector res;
    getHits(digits, digit2letter, 0, res, "");
    return res;
}
void getHits(string digits, vector digit2letter, int pos, vector &res, string cur) {
    if (pos == digits.size()) {
        if (cur.size()) res.push_back(cur);
        return;
    }
    string hits = digit2letter[digits[pos] - '0'];
    for (int i = 0; i < hits.size(); ++i) {
        cur.push_back(hits[i]);
        getHits(digits, digit2letter, pos + 1, res, cur);
        cur.pop_back();
    }
}

526. Beautiful Arrangement
解析: 1-N构成数组,满足ith整除i或者i整除ith。注意,这是要求全部满足。
边界:为空
思路:输入长度N,每次起始位置不变全部为1,中间结果(深度,即构成第i个),与顺序有关。
时间复杂度:O(n!)
int countArrangement(int N) {
    int res = 0;
    vector used(N+1,0);
    arrange(used, res, depth);
    return res;
}

void arrange(vector used, int &res, int depth) {
    for (int i = 1; i < used.size(); ++i) {
        if (used[i] == 0 && (used[i] % depth == 0 || depth % used[i] == 0)) {
            used[i] = 1;
            arrange(used, res, depth + 1);
            used[i] = 0;
        }
    }
}


357. Count Numbers with Unique Digits
解析: [0,10^N)的范围内整数中,每位完全不重复的整数个数
边界:为空
思路:0特殊处理,不能用来做首位。total = 1 ,处理了前面几位是0的情况。
时间复杂度:O(n!)
int countNumbersWithUniqueDigits(int n) {
    vector occur(10, 0);
    return count(occur, n, 0);
}

int count(vector occur, int n, int d) {
    if (n == d) {
        return 1;
    }
    int total = 1;
    for (int i = (d == 0) ? 1 : 0; i < occur.size(); ++i) {
        if (!occur[i]) {
            occur[i] = 1;
            total += count(occur, n, d + 1);
            occur[i] = 0;
        }
    }
    return total;
}

你可能感兴趣的:(9. 回溯)