【全排列】回溯算法入门

  • 案例一:无重数组全排列:
  • 算法介绍:
  • 【全排列】回溯算法入门 - 图1
  • 利用树形图判断递归结构与如何剪枝:
  • 分支如何产生
  • 题目需要的解在叶子节点还是非叶子节点,还是路径?
  • 哪些情况下会产生不必要的解:
  • 为什么产生
  • 如何剪枝
  • 代码:

  • class Solution { vector> res;private: void perm(vector& nums, int low, int high) { if (low == high) { res.emplace_back(nums); return; } for (int i = low; i <= high; ++i) { swap(nums[i], nums[low]); perm(nums, low + 1, high); swap(nums[i], nums[low]); } }public: vector> permute(vector& nums) { res.clear(); perm(nums, 0, nums.size() - 1); return res; }};
  • 案例二:带有重复数的全排列(剪枝):
  • 算法介绍:
  • 本题为上述题目的plus版,序列中包含重复的数字,要求返回不带重复数字的全排列,我们依然采用回溯方法
  • 首先要对元素进行排序,以便我们通过相邻的节点判断是否重复使用
  • 对于搜索树的同一层进行去重:
  • 【全排列】回溯算法入门 - 图2
  • 回溯算法是先按照dfs的搜索顺序搜索出一个有效解,再不断回溯到可以探索的搜索空间,因此对于搜索树的同一层来说,如果used[i - 1] == false,那么该位置放置nums[i]的解已经搜索完毕,因此可以跳过
  • 使用used[i - 1] == true也可以,对于同一路径去重,但搜索效率不如以上方法高
  • 【全排列】回溯算法入门 - 图3
  • 代码: class Solution {private: vector> res; vector path; void backtracking(vector& nums, vector& used) { if (path.size() == nums.size()) { res.emplace_back(path); return; } for (int i = 0; i < nums.size(); ++i) { if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) continue; if (used[i] == false) { used[i] = true; path.emplace_back(nums[i]); backtracking(nums, used); path.pop_back(); used[i] = false; } } }public: vector> permuteUnique(vector& nums) { res.clear(); path.clear(); vector used(nums.size(), false); sort(nums.begin(), nums.end()); backtracking(nums, used); return res; }};