算法题复习(回溯)
目錄
- base code
- 棋盤問題
- 51. N 皇后
- 37. 解數獨
- 組合問題
- 77. 組合
- 未剪枝優化
- 剪枝優化
- 216. 組合總和 III
- 未剪枝優化
- 剪枝優化
- 17. 電話號碼的字母組合
- 39. 組合總和
- 未剪枝優化
- 剪枝優化
- 40. 組合總和 II,挺重要的,涉及到去重了
- 切割問題
- 131. 分割回文串
- 子集問題
- 78. 子集
- 90. 子集 II,有樹層去重
- 491. 遞增子序列,也需要數層去重,使用unordered_set
- 排列問題
- 46. 全排列
- 47. 全排列 II + 數層去重
- 行程安排+高級容器的使用,現在還沒吃透
base code
回溯法大致思路
void backtracking(參數) {if(終止條件) {存放結果;return;}for(選擇:本層集合中的元素(樹中節點孩子的數量就是集合的大小)) {處理節點;backtracking(路徑,選擇列表); //遞歸回溯,撤銷處理結果} }棋盤問題
51. N 皇后
https://leetcode-cn.com/problems/n-queens/
class Solution { private:vector<vector<string>> result; public:bool isValid(int row, int col, vector<string>& chessboard, int n){//檢查列for(int i = 0; i < row; i++){if(chessboard[i][col] == 'Q') return false;}//檢查左上方有無for(int i = row - 1,j = col - 1; i >= 0 && j >= 0; i--, j--){if(chessboard[i][j] == 'Q') return false;}//檢查右上方有無for(int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++){if(chessboard[i][j] == 'Q') return false;}return true;}//n表示棋盤邊長,row表示當前遍歷到第幾層了void backtracking(int n, int row, vector<string>& chessboard){//當遞歸到棋盤最底層的時候,收集結果if(row == n){result.push_back(chessboard);return;}//單層邏輯,遞歸深度row控制行,每一層for循環col控制列for(int col = 0; col < n; col++){if(isValid(row,col,chessboard,n) == true){chessboard[row][col] = 'Q'; //放置皇后backtracking(n,row+1,chessboard);chessboard[row][col] = '.';}}return ;}vector<vector<string>> solveNQueens(int n) {result.clear();//注意一下棋盤的初始化方式vector<string> chessboard(n,string(n,'.'));backtracking(n,0,chessboard);return result;} };37. 解數獨
https://leetcode-cn.com/problems/sudoku-solver/
class Solution { public:bool isValid(int row, int col, char val,vector<vector<char>>& board){//判斷同行是否重復for(int i = 0; i < 9; i++){if(board[row][i] == val) return false;}//判斷同列是否重復for(int i = 0; i < 9; i++){if(board[i][col] == val) return false;}//判斷9方格里是否重復int startRow = (row / 3) * 3;int startCol = (col / 3) * 3;for(int i = startRow; i < startRow + 3; i++){for(int j = startCol; j < startCol + 3; j++){if(board[i][j] == val) return false;}}//如果都沒錯return true;}bool backtracking(vector<vector<char>>& board){for(int i = 0; i < board.size(); i++){for(int j = 0; j < board[0].size(); j++){//如果已經填入,continueif(board[i][j] != '.') continue;for(char k = '1'; k <= '9'; k++) //觀察(i,j)這個位置放置k是否合適{if(isValid(i,j,k,board)){board[i][j] = k; //放置if(backtracking(board)) return true; //如果找到一組合適的解,立刻返回board[i][j] = '.'; //回溯,撤銷k}}return false; //9個數都試完了,都不行,返回false;}}return true; //遍歷完沒有返回false,說明找到了合適的棋盤}void solveSudoku(vector<vector<char>>& board) {backtracking(board);} };組合問題
77. 組合
未剪枝優化
https://leetcode-cn.com/problems/combinations/
邏輯樹同一層:從左向右取數,取過的數不再重復取
n相當于樹寬,k相當于樹深度。
每次搜索到葉子節點,就找到了一個結果。
剪枝優化
單層邏輯中
如果for循環選擇的起始位置之后的元素個數已經不足,就沒有必要再次搜索。
已經選擇path.size(),還需要k - path.size(),所以可以這樣寫:
216. 組合總和 III
https://leetcode-cn.com/problems/combination-sum-iii/
未剪枝優化
class Solution { public:vector<vector<int>> result;vector<int> path;// 目標和 樹深度 當前path路徑和 本層起始點 void backtracking(int targetSum, int k, int sum, int startIndex){if(path.size() == k) //到達樹底部{if(sum == targetSum) result.push_back(path);return;}for(int i = startIndex; i <= 9; i++){sum += i;path.push_back(i);backtracking(targetSum,k,sum,i+1);path.pop_back();sum -= i;}}vector<vector<int>> combinationSum3(int k, int n) {result.clear();path.clear();backtracking(n,k,0,1);return result;} };剪枝優化
如果sum > targetSum ,那么往后遍歷就沒有意義了。于是在backtracking函數一開始加上:
if(sum > targetSum) return;17. 電話號碼的字母組合
https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number/
細節:
數字與字母的映射.與前兩題不同的是,本題是求不同集合之間的組合,前兩題是求同一個集合中的組合。
39. 組合總和
https://leetcode-cn.com/problems/combination-sum/
需要注意的點:
組合沒有數量要求,元素可以無限重復選取。
未剪枝優化
class Solution { public:vector<vector<int>> result;vector<int> path;void backtracking(vector<int>& candidates, int target, int sum, int startIndex){if(sum > target) return;if(sum == target) {result.push_back(path);return;}for(int i = startIndex; i < candidates.size(); i++){sum += candidates[i];path.push_back(candidates[i]);backtracking(candidates,target,sum,i);path.pop_back();sum -= candidates[i];}}vector<vector<int>> combinationSum(vector<int>& candidates, int target) {result.clear();path.clear();backtracking(candidates,target,0,0);return result;} };剪枝優化
對總集合先排序,如果本層sum + candidates[i] 已經大于target,就沒有必要再向本層之后的元素遍歷了。
class Solution { public:vector<vector<int>> result;vector<int> path;void backtracking(vector<int>& candidates, int target, int sum, int startIndex){if(sum > target) return;if(sum == target) {result.push_back(path);return;}for(int i = startIndex; i < candidates.size() && candidates[i] + sum <= target; i++){sum += candidates[i];path.push_back(candidates[i]);backtracking(candidates,target,sum,i);path.pop_back();sum -= candidates[i];}}vector<vector<int>> combinationSum(vector<int>& candidates, int target) {result.clear();path.clear();sort(candidates.begin(),candidates.end());backtracking(candidates,target,0,0);return result;} };40. 組合總和 II,挺重要的,涉及到去重了
這一題和上一題有區別:
本題candidates中的每個數字再每個組合中只能用一次,并且candidates中有元素是重復的,并且解集中不能包含重復的組合。要在搜索的過程中就去掉重復組合。
去重:使用過的元素不能重復選取。注意組合問題的邏輯樹有兩個維度:深度和廣度
元素在同一個組合內可以重復,但是兩個組合不能相同,顯然是樹層廣度上的去重。
同時注意,對于樹層去重不需要使用輔助數組。
切割問題
切割問題類似于組合問題。
131. 分割回文串
https://leetcode-cn.com/problems/palindrome-partitioning/
class Solution { public:vector<vector<string>> result;vector<string> path;bool isHuiWen(const string& s,int start,int end){int i = start;int j = end;while(i < j){if(s[i] != s[j]) return false;i++;j--;}return true;}void backtracking(const string& s,int startIndex){if(startIndex >= s.size()){result.push_back(path);return;}for(int i = startIndex; i < s.size(); i++){//如果是回文子串if(isHuiWen(s,startIndex,i)){//獲取子串string str = s.substr(startIndex,i - startIndex + 1);path.push_back(str);backtracking(s,i+1);path.pop_back();}}}vector<vector<string>> partition(string s) {result.clear();path.clear();backtracking(s,0);return result;} };子集問題
子集問題也是一種組合問題。不過不同的是組合問題是在到達葉子節點時候才將path送入result。而子集問題每次進入這一層就要把path加入result,這才是子集。
78. 子集
https://leetcode-cn.com/problems/subsets/
class Solution { public:vector<vector<int>> result;vector<int> path;void backtracking(vector<int>& nums, int startIndex){//result.push_back(path);if(startIndex > nums.size()) return;for(int i = startIndex; i < nums.size(); i++){path.push_back(nums[i]);backtracking(nums,i+1);path.pop_back();}}vector<vector<int>> subsets(vector<int>& nums) {result.clear();path.clear();backtracking(nums,0);return result;} };90. 子集 II,有樹層去重
https://leetcode-cn.com/problems/subsets-ii/
比之前加上一個樹層去重
491. 遞增子序列,也需要數層去重,使用unordered_set
https://leetcode-cn.com/problems/increasing-subsequences/
去重邏輯:
1、使用unordered_set用來記錄本層元素是否重復,重復則continue
2、觀察待插入的元素nums[i]是否是比path尾部元素大等的,否則構成不了遞增序列。
排列問題
46. 全排列
https://leetcode-cn.com/problems/permutations/
與組合的區別在于for循環從0開始,但是需要注意取過的元素不能再取,這里使用used數組進行記錄。
47. 全排列 II + 數層去重
https://leetcode-cn.com/problems/permutations-ii/
class Solution { public:vector<vector<int>> result;vector<int> path;void backtracking(vector<int>& nums, vector<bool>& used){if(path.size() == nums.size()) {result.push_back(path);return;}for(int i = 0; i < nums.size(); i++){//去重,同一層nums[i-1]使用過的話直接跳過if(i > 0 && nums[i] == nums[i-1] && used[i-1] == true) continue;//用過的元素不需要使用if(used[i] == true) continue;used[i] = true;path.push_back(nums[i]);backtracking(nums,used);path.pop_back();used[i] = false;}}vector<vector<int>> permuteUnique(vector<int>& nums) {result.clear();path.clear();sort(nums.begin(),nums.end());vector<bool> used(nums.size(),false);backtracking(nums,used);return result;} };行程安排+高級容器的使用,現在還沒吃透
https://leetcode-cn.com/problems/reconstruct-itinerary/
還得再多看看。
總結
- 上一篇: 有人懂电视盒子吗,S912的外贸盒子怎么
- 下一篇: 《c++特性》