排列组合是回溯算法的经典问题,有固定的模板写法,包括重复元素以及非重复元素。下面总结一下leetcode中的排列组合问题。

排列
排列问题一般是对原数组进行交换,然后维护一个全局变量的结果集合,每当符合要求将当前状态下的原数组加入到结果集合之中。

题目: 46 全排列

    vector<vector<int>> res;vector<vector<int>> permute(vector<int>& nums) {backtrack(nums, 0);return res;}void backtrack(vector<int> & nums, int depth){if(depth==nums.size()) res.emplace_back(nums);for(int i=depth;i<nums.size();i++){swap(nums[i], nums[depth]);backtrack(nums, depth+1);swap(nums[i], nums[depth]);}}

注意,backtrack里面depth表示的是深度,需要不断加1。

题目:47全排列 ||

包含重复元素的序列,需要返回不重复的全排列,如果不从结果出发(使用一个set<vector >保存),需要保证在每一层交换时,不能有两个重复元素别交换到头位置。

    vector<vector<int>> res;vector<vector<int>> permuteUnique(vector<int>& nums) {// sort(nums.begin(), nums.end());backtrack(nums, 0);return res;}void backtrack(vector<int> & nums, int index){if(index==nums.size()){res.push_back(nums); return ;} // 去除重复的关键在于同一深度不能有相同的元素unordered_set <int> record;for(int i = index;i<nums.size();i++){// cout << "test " << index << " " << i << endl;// 因为换完顺序之后 不能保证index之后的元素其顺序性   如果使用这种不能交换元素// for(int j=0;j<nums.size();j++) cout << nums[j] << " ";// if(i>index && nums[i]==nums[i-1]) continue;if(record.find(nums[i])!=record.end()) continue; record.insert(nums[i]);swap(nums[i], nums[index]);backtrack(nums, index+1);swap(nums[i], nums[index]);}}

组合
组合问题与排列问题不同,原数组是不能进行删除操作的,需要手动维护一个当前结果的序列(一般使用vector),然后每个元素都有添加与不添加两种选择。

这里的depth表示选择哪个元素 不再表示深度

经典的组合问题, 无重复元素,不重复解。
需要注意的是数字可以无限制被选取。

无重复元素意味着不需要对每个选择的元素进行判断

无限制选取意味着下一次还可以从当前元素选起

    vector<vector<int>> ans;vector<vector<int>> combinationSum(vector<int>& candidates, int target) {vector<int> vc;// sort(candidates.begin(), candidates.end());backtrack(candidates, vc, target,0);return ans;}void backtrack(vector<int> & candidates, vector<int> & vc, int target, int index){if(target==0){ans.emplace_back(vc);return;}if(target < 0) return;for(int i=index;i<candidates.size();i++){vc.push_back(candidates[i]);// 下标下标下标backtrack(candidates,vc,target-candidates[i],i);vc.pop_back();}}

题目 40 组合总数||

重复元素: for循环里面不能选择相同的元素
每个元素只能使用一次: 回溯函数选择一个元素i之后,下次递归时下标为i+1

    vector<vector<int>> res;vector<int> vc;vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {sort(candidates.begin(), candidates.end());// N = candidates.size();// vector<bool> visited(N, false);// memset(visited, false, (N)*sizeof(bool));backtrack(candidates, target, 0);return res;}// 下一次元素位置位于i+1位置void backtrack(vector<int> candidates, int target, int index){if(target==0) {res.push_back(vc);return;}for(int i=index;i<candidates.size();i++){// 利用升序性质if(target-candidates[i]<0) break;if(i>index && candidates[i] == candidates[i-1]) continue;vc.push_back(candidates[i]);backtrack(candidates, target-candidates[i], i+1);vc.pop_back();}

需要先进行一次排序,方便for循环里面根据前后元素大小去除重复解。
然后另外需要注意的就是递归的时候下标为i+1.

题目: 216 组合总和 |||

题目分析:本体本质上还是组合问题,但是需要注意的是引入了深度,需要根据判断是否找到解。
还有一点就是没有一个可操作数组,直接使用index同时代表下标以及数值即可

    vector<vector<int>> res;vector<int> vc;vector<vector<int>> combinationSum3(int k, int n) {backTrack(1, n, k, 0);return res;}// index表示选择的起始值, depth表示当前深度用于判断是否到达kvoid backTrack(int index,int target, int k, int depth){if(target==0){if(depth==k){res.push_back(vc);}return;}if(target<0 || depth> k) return;for(int i=index; i<=9;i++){vc.push_back(i);backTrack(i+1, target-i, k, depth+1);vc.pop_back();}}

题目:377 组合总和IV

题目分析:

不限制选择次数,那么只要保证每次选择的不是同一个数字,那么最后得到的就是不同解。
与前面的组合问题区别在于, 1 1 2 与 1 2 1算是两种不同解,这样就没有顺序之分了。

记忆化递归解法:

    unordered_map<int, int> map;int combinationSum4(vector<int>& nums, int target) {return backTrack(nums, target);}int backTrack(vector<int> & nums,  int target){if(target==0) {return 1;}if(target < 0) return 0;if(map.find(target)!=map.end()) return map[target];int sum = 0;for(int i=0;i<nums.size();i++){sum +=backTrack(nums, target-nums[i]);}map[target] = sum;return sum;}

动态规划解法:

    // 完全背包问题int combinationSum4(vector<int>& nums, int target) {unsigned long long dp[target+1];memset(dp, 0,sizeof(dp));dp[0] = 1;for(int i=1;i<=target;i++){for(int j=0;j<nums.size();j++){if(nums[j]<=i){dp[i] += dp[i-nums[j]];}}}return (int)dp[target];}

排列变种:

题目: 31下一个排列


题目分析:
根据当前的排列,1 2 3-> 1 3 2等 ,如果想求出下一个排列,肯定是想让更大的数字能够提前一些与前面的小数进行交换。问题是怎么能够刚好能够比当前排列大一点点呢?
希望下一个数增加的幅度尽可能小,那么就需要

  1. 尽可能在靠右的位置进行交换,从后往前查找这个位置
  2. 将一个尽可能小的大数字与前面的小数进行交换 1 2 3 4 6 5 应该将5 与4 进行交换,而不是6
  3. 将大数字换完之后,后面的数字需要从小到大排列。

转化为算法的思想就是:
4在这里插入代码片. 从后往前查找第一个相邻的升序对,满足A[i-1] < A[i]
5. 在i之后的元素中找到最小的可以满足大于A[i-1]的元素,与A[i-1]进行交换
6. 将i-1与end之间的元素进行reverse

    void nextPermutation(vector<int> & nums){int l = nums.size()-1;// 找到第一个逆序 0 1 5 4 3while(l>=1 && nums[l] <= nums[l-1]){l--;}// l-1之后是降序序列 reverse(nums.begin()+l, nums.end());if(l>0){for(int i=l;i<nums.size();i++){if(nums[i]>nums[l-1]) {swap(nums[i], nums[l-1]);break;}}}}

题目:60 第k个排列

题目分析:
m个元素的排列个数为m!个,题目给定n个元素以及求解的第k个排列。
根据 k-1/(n-1)!的值,可以确定排列中的第一个元素。
比如 k = (n-1)! 时, 此时第一个元素应该是1,而不是2,这就是为什么提前k-1的原因。
然后k%=(n-1)!,重复操作直到求解出n个元素的值。
需要注意的一点是: 在取元素的时候需要不断将已确定的元素删除掉。

    string getPermutation(int n, int k){int f[10];f[0] = 1;for(int i=1;i<=n;i++) f[i] = i*f[i-1];string s = "123456789";return getkPermutation(s, k, f, n);}string getkPermutation(string &s,int k, int f[],  int n){// 小于结果的有k-1个 k = k -1;string res = "";for(int i=n-1;i>=0;i--){int base = f[i];int index = k/base;k = k%base;res+=s[index];s.erase(s.begin()+index);}return res;}

题目:leetcode 回文全排列||

题目分析:
统计字符串中字符出现次数,等奇数个数超过1的时候,表示不能有回文串。
对于偶数次数的字符,应该是一半位于左侧,一半位于右侧,将字符加入空字符串中,加入num/2次,num为出现次数。
然后就是重复元素的全排列问题。

vector<string> res;
void permute(string t, int depth, string mid)
{if(depth==t.size()){res.push_back(t+mid+string(t.rbegin(), t.rend()));return;}unordered_set<char> set;for(int i=depth;i<t.size();i++){if(set.find(t[i])!=set.end()) continue;set.insert(t[i]);swap(t[i],t[depth]);permute(t,depth+1,mid);swap(t[i],t[depth]);}}vector<string> generatePalindroms(string s)
{unordered_map<char, int> map;int count = 0;// 奇数个数for(auto a:s) ++map[a];string mid = "";string t = "";for(auto item: map){if(item.second%2==1) mid+=item.first;else t+=string(item.second/2, item.first);if(mid.size() > 1) return{};}permute(t, 0, mid);return res;}

leetcode 排列组合系列相关推荐

  1. 排列组合-9.1 排列组合系列问题

    题目1: 在6x9的方格中,以左上角为起点,右下角为终点,每次只能向下走或者向右走,请问一共有多少种不同的走法? 答:无论怎么走,都需要向下走5次,向右8次,共13步.因此可以选择13步中哪几步向下( ...

  2. [leetcode]排列组合子集(python)

    文章目录 排列 全排列不重复,T46 排列有重复不可复选,T47 排列无重复可复选 组合 组合不重复不可复选,T77 组合总和有重复不可复选,T40 组合总和无重复可复选,T39 子集 子集不重复不可 ...

  3. 算法 64式 17、排列组合算法整理

    1算法思想 排列组合 1.1含义 排列: 含义:从元素列表中取出指定个数的元素进行排序 公式:从n个不同元素中取出m个元素的排列个数=n!/(n-m)! 组合: 含义:从元素列表中取出指定个数的元素, ...

  4. LeetCode 216. 组合总和 III(排列组合 回溯)

    1. 题目 找出所有相加之和为 n 的 k 个数的组合.组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字. 说明: 所有数字都是正整数. 解集不能包含重复的组合. 示例 1: 输 ...

  5. LeetCode 40. 组合总和 II(排列组合 回溯)

    1. 题目 给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合. candidates 中的每个数字在每个组合中只 ...

  6. [leetcode] 题型整理之排列组合

    一般用dfs来做 最简单的一种: 17. Letter Combinations of a Phone Number Given a digit string, return all possible ...

  7. LeetCode:数组(排列组合,二分查找I,二分查找II)

    1,排列组合 38,剑指Offer:字符串的排列 题目:输入一个字符串,打印出该字符串中字符的所有排列.你可以以任意顺序返回这个字符串数组,但里面不能有重复元素. class Solution {Li ...

  8. 【亡羊补牢】挑战数据结构与算法 第18期 LeetCode 面试题 08.08. 有重复字符串的排列组合(递归与回溯)

    仰望星空的人,不应该被嘲笑 题目描述 有重复字符串的排列组合.编写一种方法,计算某字符串的所有排列组合. 示例1: 输入:S = "qqe"输出:["eqq", ...

  9. LeetCode 例题精讲 | 08 排列组合问题:回溯法的候选集合

    点击关注上方"五分钟学算法", 设为"置顶或星标",第一时间送达干货. 转自面向大象编程 本期例题:LeetCode 46 - Permutations[1]( ...

  10. leetcode面试题 08.08. 有重复字符串的排列组合(回溯)

    有重复字符串的排列组合.编写一种方法,计算某字符串的所有排列组合. 示例1: 输入:S = "qqe" 输出:["eqq","qeq",&q ...

最新文章

  1. Linux——进程控制(总结)
  2. MFC DLL 的三种类型
  3. 枚举类 - Java面向对象编程
  4. 图像工程CH4 频域图像增强
  5. 在RelativeLayout中对控件的显示隐藏操作问题
  6. AUTOSAR从入门到精通100讲(十五)-AURIX TC3xx MCAL中Link文件解析以及代码变量定位方法详解
  7. 【linux】学习6
  8. The driver is automatically registered via the SPI and manual loading of the
  9. 是驴是骡,遛一遛就知道了
  10. 求cosx计算公式 【问题描述】 已知cosx的近似计算公式如下: cosx = 1 - x2/2! + x4/4! - x6/6! + ... + (-1)nx2n/(2n)!
  11. easydarwin 安装_win10安装EasyDarwin
  12. jmeter 录制回放工具badboy 下载安装步骤
  13. OSI七层模式简单通俗理解
  14. 安卓系统强制旋转屏幕实现横竖屏切换
  15. MAC 安装homebrew流程
  16. 【学习OpenCV4】进度条实现总结
  17. 激活函数 ReLU、sigmoid、tanh(双曲正切)
  18. 简单使用萤石云,实时直播,监控回放
  19. 韩信点兵的问题求解方法
  20. 线性代数(十六) : 矩阵的左零空间及四个基本子空间总结

热门文章

  1. 手写朴素贝叶斯文本分类
  2. 大数据-浅谈hive优化
  3. 2022年数字IC设计提前批笔试面试全流程分享(前期海投,后期顺利拿到Offer)
  4. python爬虫代理ip
  5. 显卡更新显示计算机无法识别,显卡驱动更新不了怎么办 显卡驱动更新不了解决方法...
  6. 鼠标自动不停地按右键
  7. windows系统更新失败无法启动的解决方法
  8. 阿里金融云操作教程Plus
  9. 小米系统shell_廉颇老矣,尚能饭否?小米平板1刷机 lineageOS/CM焕发青春
  10. 少儿编程学习(顺序结构)