LeetCode刷题之回溯算法
LeetCode之搜索专题
1.回溯算法
回溯方法基本套路:
result = [];
def backtrack():
if 满足结束条件:
result.add(路径)
return
for 选择 in 选择列表:
做选择
backtrack(路径, 选择列表)
撤销选择
1.LeetCode之[N皇后问题]
-
解题思路
-
代码实现
1.递归解法(回溯方法基本套路)
/* * @lc app=leetcode.cn id=51 lang=cpp * * [51] N 皇后 */ // @lc code=start class Solution { public: vector<vector<string>> res; vector<vector<string>> solveNQueens(int n) { vector<string> board(n, string(n, '.')); backtrace(board, 0); return res; } void backtrace(vector<string> &board, int row) { if (row == board.size()) { res.push_back(board); return; } int n = board[row].size(); for (int col = 0; col < n; col++) { if (!isValid(board, row, col)) { continue; } board[row][col] = 'Q'; backtrace(board, row + 1); board[row][col] = '.'; } } //是否能在board[row][col]放置皇后 bool isValid(vector<string> &board, int row,int col) { int n = board.size(); //检查列是否有皇后冲突 for (int i = 0; i < n; i++) { if (board[i][col] == 'Q') { return false; } } //检查右上方是否有冲突 for (int i = row - 1, j = col + 1; i >= 0 && j < board[0].size(); i--, j++) { if (board[i][j] == 'Q') { return false; } } //检查左上方是否有皇后冲突 for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--,j--) { if (board[i][j] == 'Q') { return false; } } return true; } }; // @lc code=end
2.非递归解法(回溯方法)
class Solution { public: vector<vector<string>> res; int a[10]; vector<vector<string>> solveNQueens(int n) { vector<string> nums(n,string(n,'.')); int k = 0; a[k] = 0; while (k >= 0) { if (k < n && a[k] < n) { if (isValid(nums, k ,a[k])) { nums[k][a[k]] = 'Q'; k++; a[k] = 0; } else { a[k]++; } } else { if (k >= n) { res.push_back(nums); } k--; if (k < 0) break; nums[k][a[k]] = '.'; a[k]++; } } return res; } bool isValid(vector<string> &board, int row, int col) { int n = board.size(); //检查列是否有皇后冲突 for (int i = 0; i < n; i++) { if (board[i][col] == 'Q') { return false; } } //检查右上方是否有冲突 for (int i = row - 1, j = col + 1; i >= 0 && j < board[0].size(); i--, j++) { if (board[i][j] == 'Q') { return false; } } //检查左上方是否有皇后冲突 for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--,j--) { if (board[i][j] == 'Q') { return false; } } return true; } };
2.LeetCode之46全排列问题
-
解题思路
-
采用回溯算法(递归)
-
采用交换元素的方法,也用到递归思想
-
回溯非递归算法
模仿上述N皇后非递归算法
-
-
代码实现
1.回溯算法之递归
/* * @lc app=leetcode.cn id=46 lang=cpp * * [46] 全排列 */ // @lc code=start class Solution { public: vector<vector<int>> res; vector<vector<int>> permute(vector<int>& nums) { vector<int> track; backtrace(nums, track); return res; } void backtrace(vector<int> nums, vector<int> track) { if(track.size() == nums.size()) { res.push_back(track); return; } for (int i = 0; i < nums.size(); i++) { if (find_element(track, nums[i])) { continue; } track.push_back(nums[i]); backtrace(nums, track); track.pop_back(); } } bool find_element(vector<int> num, int n) { for (const auto & d : num) { if(n == d) return true; } return false; } }; // @lc code=end
2.交换元素之递归
class Solution { public: vector<vector<int>> res; vector<vector<int>> permute(vector<int>& nums) { int left = 0, right = nums.size() - 1; fullArr(nums, left, right); return res; } void fullArr(vector<int> &nums, int left, int right) { if (left == right) { res.push_back(nums); return; } for (int i = left; i <= right; i++) { swap(nums, left, i); fullArr(nums, left + 1, right); swap(nums, left, i); } } void swap(vector<int> &nums, int i, int j) { int temp = nums[i]; nums[i] = nums[j]; nums[j] = temp; } };
3.回溯非递归算法
class Solution { public: vector<vector<int>> res; int a[7] = { 0 }; void swap(vector<int>& nums, int i, int j) { int temp = nums[i]; nums[i] = nums[j]; nums[j] = temp; } bool isValid(vector<int>& nums, int n) { for (const auto& d : nums) { if (d == n) return false; } return true; } vector<vector<int>> permute(vector<int>& nums) { vector<int> temp; int len = nums.size(); int k = 0; while (k >= 0) { if (k < len && a[k] < len) { if (isValid(temp, nums[a[k]])) { temp.push_back(nums[a[k]]); k++; a[k] = 0; } else { a[k]++; } } else { if (k == len) { res.push_back(temp); } k--; if (k < 0) break; temp.pop_back(); a[k]++; } } return res; } };
3.LeetCode之698划分为K个相等的子集
-
解题思路
-
回溯加经典递归
这里有两种思路,我们把数字分成K个和相等的集合,可以将K个集合看成是K个桶,然后我们需要将将数字放到桶里面。这里有两种视角,一种是以数字的角度去看待,遍历每一个数字,判断应该放在哪一个桶里。或者以桶的角度去看,遍历每一个桶,判断哪些数字应该放在桶里。
-
-
代码实现
1.以数字的角度去看待
/* * @lc app=leetcode.cn id=698 lang=cpp * * [698] 划分为k个相等的子集 */ // @lc code=start class Solution { public: bool canPartitionKSubsets(vector<int>& nums, int k) { if (k > nums.size()) return false; int sum = 0; for (auto i : nums) { sum += i; } if (sum % k != 0) return false; int target = sum / k; vector<int> bucket(k); int index = 0; sort(nums.begin(), nums.end(), [](int a,int b){ return a > b; }); return backtrack(nums, index, bucket, target); } bool backtrack(vector<int> &nums, int index, vector<int> &bucket, int target) { if (index == nums.size()) { for (auto n : bucket) { if (n != target) { return false; } } return true; } for (int i = 0; i < bucket.size(); i++) { if (bucket[i] + nums[index] > target) { continue; } bucket[i] += nums[index]; if(backtrack(nums, index + 1, bucket, target)) { return true; } bucket[i] -= nums[index]; } return false; } }; // @lc code=end
2.以桶的角度去看待
class Solution { public: bool canPartitionKSubsets(vector<int>& nums, int k) { if (k > nums.size()) { return false; } int sum = 0; for (auto i : nums) { sum += i; } if (sum % k != 0) { return false; } int target = sum /k; vector<bool> used(nums.size(),false); return backtrack(k, 0, 0, used, target, nums); } bool backtrack(int index, int start, int bucket,vector<bool> &used, int target, vector<int> &nums) { if (index == 0) { return true; } if (bucket == target) { return backtrack(index - 1, 0, 0, used, target, nums); } for (int i = start; i < nums.size(); i++) { if (used[i]) { continue; } if (nums[i] + bucket > target) { continue; } used[i] =true; bucket += nums[i]; if (backtrack(index , i + 1, bucket, used, target, nums)) { return true; } used[i] = false; bucket -= nums[i]; } return false; } };
4.LeetCode之78子集
-
解题思路
- 利用数学归纳法的思想
- 回溯求解
-
代码实现
1.利用数学归纳法
class Solution { public: vector<vector<int>> subsets(vector<int>& nums) { if (nums.empty()) return {{}}; int n = nums.back(); nums.pop_back(); vector<vector<int>> res = subsets(nums); int size = res.size(); for (int i = 0; i < size; i++) { res.push_back(res[i]); res.back().push_back(n); } return res; } };
2.回溯求解(画出决策树)
/* * @lc app=leetcode.cn id=78 lang=cpp * * [78] 子集 */ // @lc code=start class Solution { public: vector<vector<int>> res; vector<vector<int>> subsets(vector<int>& nums) { vector<int> track; backtrack(nums, 0, track); return res; } void backtrack(vector<int> &nums, int start, vector<int> &track) { res.push_back(track); for (int i = start; i < nums.size(); i++) { track.push_back(nums[i]); backtrack(nums, i+1, track); track.pop_back(); } } }; // @lc code=end
5.LeetCode之77组合
-
解题思路
- 回溯(想象出决策树,利用回溯进行深度优先遍历)
-
代码实现
/* * @lc app=leetcode.cn id=77 lang=cpp * * [77] 组合 */ // @lc code=start class Solution { public: vector<vector<int>> res; vector<vector<int>> combine(int n, int k) { vector<int> track; vector<int> nums; for (int i = 1; i <= n; i++) { nums.push_back(i); } backtrack(nums, 0, 0, k, track); return res; } void backtrack(vector<int> &nums, int k, int start, int current, vector<int> &track) { if (k == current) { res.push_back(track); return; } for (int i = start; i <nums.size(); i++) { track.push_back(nums[i]); backtrack(nums, k+1, i + 1, current, track); track.pop_back(); } } }; // @lc code=end