查看: 1562| 回复: 1
跳转到指定楼层
上一主题 下一主题
收起左侧

[Leetcode] 排列组合题的框架思路和题目总结

全局:

注册一亩三分地论坛,查看更多干货!

您需要 登录 才可以下载或查看附件。没有帐号?注册账号

x
本帖最后由 liux0656 于 2019-8-26 03:28 编辑

总的来说,都是需要用到回溯法

回溯法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。

通用算法思路总结:

  • 初始结果列表。
  • 可能要将数集排序,方便处理重复元素的情况。
  • 调用递归函数。
  • 书写递归函数,先要考虑原点状况,一般就是考虑什么情况下要将当前结果添加到结果列表中。
  • for循环遍历给定集合所有元素,不同题目区别在于进行循环的条件,具体看例子。每当一个元素添加到当前结果中之后,要再调用递归函数,相当于固定了前缀穷举后面的变化。
  • 调用完之后要将当前结果中最后一个元素去掉,进行下一个循环才不会重复。


算法框架


  1.     // 算法框架
  2.     public void backtracting(temp){
  3.         if("temp是一个结果"){  //结果收集条件
  4.             加入结果集;
  5.             return;
  6.         }
  7.         for(j=start;j<= end;j++){
  8.             if("不满足加入条件") continue;
  9.             temp.add(a); //加入当前元素
  10.             backtracting(j+1); //继续进行下一步搜索
  11.             temp.remove(a); //回溯的清理工作,把上一步的加入结果删除
  12.         }
  13.     }
  14.     //求存在解的算法框架
  15.     public boolean backtracting(temp){
  16.         if("temp是一个结果"){  //结果收集条件
  17.             加入结果集;
  18.             return true;
  19.         }
  20.         for(j=start;j<= end;j++){
  21.             if("不满足加入条件") continue;
  22.             temp.add(a); //加入当前元素
  23.             if(backtracting(j+1))
  24.                 return true; //继续进行下一步搜索
  25.             temp.remove(a); //回溯的清理工作,把上一步的加入结果删除
  26.             return false;
  27.         }
  28.     }
复制代码


leetcode 题库

17. 电话号码的字母组合

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。
输入:"23"
输出:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].

首先用一个数组把每个数字按键对应的字母存储起来,再把给定的字符串digits拆分成每一个单独的数字做处理。使用一个辅助函数进行处理

private void helper (String digits, int i,  StringBuilder cur, List<String> ans);
/*
String digits : 给定的数字组合字符串
int i :当前(递归)处理的层数
StringBuilder cur :当前字母组合
List<String> ans :结果列表
*/

处理过程如下:

取出当前数字,以及该数字按键对应的字母,存为一个char数组curLetters
对curLetters进行遍历,将当前字符加入当前字母组合cur,然后(递归地)处理后一个数字
当层数等于给定字符串digits长度时,将当前字母组合cur加入结果列表ans,删除刚用过的字符(也就是当前字母组合的最后一个字母)
遍历完成后,结果列表ans就是题目要求的结果

  1. private final static String[] digitsToChars =
  2.             {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};

  3.     public List<String> letterCombinations (String digits) {
  4.         List<String> ans = new ArrayList<>();
  5.         if (digits == null || digits.length() == 0) {
  6.             return ans;
  7.         }
  8.         doCombination(new StringBuilder(), ans, digits);
  9.         return ans;
  10.     }

  11.     private void doCombination (StringBuilder prefix, List<String> combination, String digits) {
  12.         if (prefix.length() == digits.length()) {
  13.             combination.add(prefix.toString());
  14.         } else {
  15.             int curDiggit = digits.charAt(prefix.length()) - '0';
  16.             for (char c : digitsToChars[curDiggit].toCharArray()) {
  17.                 prefix.append(c);
  18.                 doCombination(prefix,combination,digits);
  19.                 prefix.deleteCharAt(prefix.length() - 1);
  20.             }
  21.         }
  22.     }
复制代码



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:

[
  "((()))",
  "(()())",
  "(())()",
  "()(())",
  "()()()"
]
题目:求出所有有效括号的全排列
思路:针对一个长度为2n的合法排列,第1到2n个位置都满足如下规则:左括号的个数大于等于右括号的个数

设left和right分别为剩余的左右括号数目,则使用递归求解可以分为以下几种情况
1、left>0  可以继续加括号
2、left=0 and right=0 结果收集
3、right>0 还需要满足right>left加入右括号

  1. public class Solution {
  2.     public List<String> list = new LinkedList<String>();
  3.     public List<String> generateParenthesis(int n) {
  4.         if(n == 0)
  5.             return list;
  6.         generate(n,n,"",list);
  7.         return list;

  8.     }
  9.     public void generate(int left,int right,String res,List<String> list){
  10.         if(left == 0 && right == 0){
  11.             list.add(res);
  12.             return;
  13.         }
  14.         if(left>0){
  15.             generate(left-1,right,res+"(",list);
  16.         }
  17.         if(right>0 && right>left){
  18.             generate(left,right-1,res+")",list);
  19.         }

  20.     }
  21. }
复制代码



39.Combination Sum

给一个数集和一个数字target,要求返回 用数集中数字加和等于target的所有组合 (数集中的数可以重复使用)。
DFS + Backtracking。 这次是考察重复元素如何处理。依然使用回溯的template
  1. public class Solution {
  2.     List<List<Integer>> result;
  3.     public List<List<Integer>> combinationSum(int[] candidates, int target) {
  4.         result = new ArrayList<>();
  5.         Arrays.sort(candidates);
  6.         helper(candidates,new ArrayList<>(),target,0);
  7.         return result;
  8.     }
  9.    
  10.     private void helper(int[] candidates,List<Integer> temp, int remain,int start){
  11.         if(remain == 0) {
  12.             result.add(new ArrayList<>(temp));
  13.             return;
  14.         }
  15.         
  16.         for(int i = start;i < candidates.length && remain >= candidates;i++){
  17.                 int tempRemain = remain - candidates;
  18.                 if(tempRemain == 0 || tempRemain >= candidates){
  19.                     temp.add(candidates);
  20.                     
  21.                     //此处用i,因为可以重复使用元素
  22.                     helper(candidates,temp,tempRemain,i);
  23.                     temp.remove(temp.size()-1);
  24.                 }
  25.             }
  26.         
  27.     }
  28. }
复制代码



40.Combination Sum II

给一个数集和一个数字target,要求返回 用数集中数字加和等于target的所有组合 (数集中的数不可以重复使用)。

这道题不能重复使用,只需要在之前的基础上修改两个地方即可,首先在递归的for循环里加上if(i > start && candidates == candidates[i-1])  continue; 这样可以防止res中出现重复项,然后就在递归调里面的参数换成i+1,这样就不会重复使用数组中的数字了

  1. public class Solution {
  2.     List<List<Integer>> result;
  3.     public List<List<Integer>> combinationSum2(int[] candidates, int target) {
  4.         result = new ArrayList<>();
  5.         Arrays.sort(candidates);
  6.         helper(candidates,new ArrayList<>(),0,target);
  7.         return result;
  8.     }

  9.     private void helper(int[] candidates, List<Integer> temp,int start ,int remain){
  10.         if(remain == 0){
  11.             result.add(new ArrayList<>(temp));
  12.             return;
  13.         }
  14.         //加入remain判断条件,跳过不必要的循环。
  15.         for(int i = start; i < candidates.length && remain >= candidates;i++){
  16.             if(i > start && candidates == candidates[i-1]) continue;
  17.             int tempRemain = remain - candidates;
  18.             if(tempRemain == 0 || tempRemain >= candidates){
  19.                 temp.add(candidates);
  20.                 helper(candidates,temp,i+1,tempRemain);
  21.                 temp.remove(temp.size()-1);
  22.             }
  23.         }
  24.     }
  25. }
复制代码



46.Permuation

给一个数集(无重复元素),返回所有排列。

题目:求出0一串不同的数字的全排列。
思路:1、回溯法深搜,每次都从0------end搜索,不过需要有一个标记数组,来记录哪些已经访问过了,回溯的时候加入的元素以及标记都需要清除。

  1. public class Solution {
  2.     List<List<Integer>> result;
  3.     public List<List<Integer>> permute(int[] nums) {
  4.         result = new ArrayList<>();
  5.         if(nums.length == 0) return result;
  6.         helper(nums,new ArrayList<>());
  7.         return result;
  8.     }

  9.     private void helper(int[] nums,List<Integer> temp){
  10.         if(temp.size() == nums.length)
  11.             result.add(new ArrayList<>(temp));
  12.         else{
  13.             for(int i = 0;i<nums.length;i++){
  14.                 if(temp.contains(nums)) continue;
  15.                 temp.add(nums);
  16.                 helper(nums,temp);
  17.                 temp.remove(temp.size()-1);
  18.             }
  19.         }
  20.     }
  21. }
复制代码



47. Permutations II

题目:给出含有相同数字的元素,求所有元素的排列。
思路:当nums == nums[i-1] 时候,直接进行下一轮搜索,因为如果进行深搜索的话,得出的结果会和之前的重复。即在if(条件处)限定具体的条件。

  1. public class Solution {
  2.     public List<List<Integer>> list = new LinkedList<List<Integer>>();
  3.     public List<List<Integer>> permuteUnique(int[] nums) {
  4.         Arrays.sort(nums);
  5.         if(nums.length == 0)
  6.             return list;
  7.         int[] visited = new int[nums.length+1];
  8.         helper(nums,new LinkedList<Integer>(),visited);
  9.         return list;

  10.     }
  11.     public void helper(int[] nums,List<Integer> temp,int[] visited){
  12.         if(temp.size() == nums.length){
  13.             list.add(new LinkedList<Integer>(temp));
  14.             return;
  15.         }
  16.         for(int i=0;i<nums.length;i++){
  17.             if (i>0 && nums==nums[i-1]&&visited[i-1]==1) continue;  //具体的限定nums[i-1]==nums则进行下一轮搜索,同时引入标记矩阵。
  18.             if(visited == 0){
  19.                 visited=1;
  20.                 temp.add(nums);
  21.                 helper(nums,temp,visited);
  22.                 visited=0;
  23.                 temp.remove(temp.size()-1);
  24.             }

  25.         }

  26.     }
  27. }
复制代码



77.Combinations

给一个正整数n,要求返回1-n中k(k<=n)个数所有组合的可能。

  1. public class Solution {
  2.     List<List<Integer>> ret;
  3.     public List<List<Integer>> combine(int n, int k) {
  4.         ret = new ArrayList<>();
  5.         if(n<k || n*k==0)
  6.             return ret;
  7.         int start = 1;
  8.         List<Integer> temp = new ArrayList<>();
  9.         helper(start,n,k,temp);
  10.         return ret;
  11.     }

  12.     private void helper(int start,int n,int k,List<Integer> temp){
  13.         if(temp.size() == k){
  14.             ret.add(new ArrayList<Integer>(temp));
  15.         } else {
  16.             for(int i = start;i<=n;i++){
  17.                 temp.add(i);
  18.                 helper(i+1,n,k,temp);
  19.                 temp.remove(temp.size()-1);
  20.             }
  21.         }

  22.     }
  23. }
复制代码



78.Subsets

给一个数集(无重复数字),要求列出所有子集。

  1. public class Solution {
  2.     List<List<Integer>> res;
  3.     public List<List<Integer>> subsets(int[] nums) {
  4.         res = new ArrayList<>();
  5.         if(nums.length == 0)
  6.             return res;
  7.         List<Integer> temp = new ArrayList<>();
  8.         helper(nums,temp,0);
  9.         return res;

  10.     }

  11.     private void helper(int[] nums, List<Integer> temp,int i) {
  12.         res.add(new ArrayList<>(temp));

  13.         for(int n = i; n < nums.length;n++){
  14.             temp.add(nums[n]);
  15.             helper(nums,temp,n+1);
  16.             temp.remove(temp.size()-1);
  17.         }

  18.     }
  19. }
复制代码



90.SubsetsII

给一个数集(有重复数字),要求列出所有子集。

  1. public List<List<Integer>> subsetsWithDup(int[] nums) {
  2.     List<List<Integer>> list = new ArrayList<>();
  3.     Arrays.sort(nums);
  4.     helper(list, new ArrayList<>(), nums, 0);
  5.     return list;
  6. }

  7. private void helper(List<List<Integer>> list, List<Integer> tempList, int [] nums, int start){
  8.     list.add(new ArrayList<>(tempList));
  9.     for(int i = start; i < nums.length; i++){
  10.         if(i > start && nums == nums[i-1]) continue; // skip duplicates
  11.         tempList.add(nums);
  12.         helper(list, tempList, nums, i + 1);
  13.         tempList.remove(tempList.size() - 1);
  14.     }
  15. }
复制代码



131.Panlindrome Partitioning

给定一个回文字符串,要求将其分成多个子字符串,使得每个子字符串都是回文字符串,列出所有划分可能。

  1. public class Solution {
  2.     List<List<String>> result;
  3.     public List<List<String>> partition(String s) {
  4.         result = new ArrayList<>();
  5.         helper(new ArrayList<>(),s,0);
  6.         return result;
  7.     }

  8.     private void helper(List<String> temp, String s, int start){
  9.         if(start == s.length()){
  10.             result.add(new ArrayList<>(temp));
  11.             return;
  12.         }
  13.         for(int i = start; i < s.length();i++){
  14.             if(isPanlidrome(s,start,i)){
  15.                 temp.add(s.substring(start,i+1));
  16.                 helper(temp,s,i+1);
  17.                 temp.remove(temp.size()-1);
  18.             }
  19.         }
  20.     }

  21.     private boolean isPanlidrome(String s, int low ,int high){
  22.         while(low < high)
  23.             if(s.charAt(low++) != s.charAt(high--)) return false;
  24.         return true;
  25.     }
  26. }
复制代码




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
思路:和前面的题类似,回溯下界1 上界9 ,收集的时候限定条件改为 temp.size() == k && target == 0

  1. public class Solution {
  2.     List<List<Integer>> res = new LinkedList<List<Integer>>();
  3.     public List<List<Integer>> combinationSum3(int k, int n) {
  4.         if(k == 0){
  5.             return res;
  6.         }
  7.         hleper(n,k,1,new LinkedList<Integer>());
  8.         return res;
  9.     }
  10.     public void hleper(int sum,int k,int start,List<Integer> templist){
  11.         if(sum < 0) return;
  12.         if(sum == 0 && templist.size()==k) {
  13.             List<Integer> li = new ArrayList<Integer>(templist);
  14.             res.add(li);
  15.             return;
  16.         }
  17.         for(int i=start;i<=9;i++){
  18.             templist.add(i);
  19.             hleper(sum-i,k,i+1,templist);
  20.             templist.remove(templist.size()-1);
  21.         }

  22.     }
  23. }
复制代码



526. Beautiful Arrangement

题目:这道题给了我们1到N,总共N个正数,然后定义了一种优美排列方式,对于该排列中的所有数,如果数字可以整除下标,或者下标可以整除数字,那么我们就是优美排列,让我们求出所有优美排列的个数。
思路:pos表示下标,排列完成,并记录排列位置,visited是否被访问,使用回溯。在进行下一步的搜索的时候条件
为 pos%i ==0 || i%pos == 0 && visited = 0 没有被访问过并且可以被收集。收集条件为pso>n。

  1. class Solution {public:
  2.     int countArrangement(int N) {
  3.         int res = 0;
  4.         vector<int> visited(N + 1, 0);
  5.         helper(N, visited, 1, res);
  6.         return res;
  7.     }
  8.     void helper(int N, vector<int>& visited, int pos, int& res) {
  9.         if (pos > N) {
  10.             ++res;
  11.             return;
  12.         }
  13.         for (int i = 1; i <= N; ++i) {
  14.             if (visited == 0 && (i % pos == 0 || pos % i == 0)) {
  15.                 visited = 1;
  16.                 helper(N, visited, pos + 1, res);
  17.                 visited = 0;
  18.             }
  19.         }
  20.     }
  21. };
复制代码


698. Partition to K Equal Sum Subsets
Given an array of integers nums and a positive integer k, find whether it's possible to divide this array into k non-empty subsets whose sums are all equal.

  1. public boolean canPartitionKSubsets(int[] nums, int k) {
  2.         int sum = 0;
  3.         for(int num:nums)sum += num;
  4.         if(k <= 0 || sum%k != 0)return false;
  5.         int[] visited = new int[nums.length];
  6.         return canPartition(nums, visited, 0, k, 0, 0, sum/k);
  7.     }

  8.     public boolean canPartition(int[] nums, int[] visited, int start_index, int k, int cur_sum, int cur_num, int target){
  9.         if(k==1)return true;
  10.         if(cur_sum == target && cur_num>0)return canPartition(nums, visited, 0, k-1, 0, 0, target);
  11.         for(int i = start_index; i<nums.length; i++){
  12.             if(visited == 0){
  13.                 visited = 1;
  14.                 if(canPartition(nums, visited, i+1, k, cur_sum + nums, cur_num++, target))return true;
  15.                 visited = 0;
  16.             }
  17.         }
  18.         return false;
  19.     }
复制代码


784. Letter Case Permutation

Given a string S, we can transform every letter individually to be lowercase or uppercase to create another string.  Return a list of all possible strings we could create.

这个题要求数字保留,字母分成大小写两种。使用回溯法就是分类成数字和字母,字母再分为大写和小写继续。

要注意的一点是不需要使用for循环了。做39. Combination Sum题目的时候使用for循环的目的是能在任意位置起始求和得到目标。本题不需要从任意位置开始。

  1. class Solution {
  2. public:
  3.     vector<string> letterCasePermutation(string S) {
  4.         vector<string> res;
  5.         helper(S, res, {}, 0);
  6.         return res;
  7.     }
  8.     void helper(const string S, vector<string>& res, string path, int start) {
  9.         if (start == S.size()) {
  10.             res.push_back(path);
  11.             return;
  12.         }
  13.         if (S[start] >= '0' && S[start] <= '9') {
  14.             helper(S, res, path + S[start], start + 1);
  15.         } else {
  16.             helper(S, res, path + (char)toupper(S[start]), start + 1);
  17.             helper(S, res, path + (char)tolower(S[start]), start + 1);
  18.         }
  19.     }
  20. };
复制代码


1079. Letter Tile Possibilities

You have a set of tiles, where each tile has one letter tiles printed on it.  Return the number of possible non-empty sequences of letters you can make.

  1. public int numTilePossibilities(String tiles) {
  2.         boolean[] used = new boolean[tiles.length()];
  3.         HashSet<String> list = new HashSet<>();
  4.         dfs(tiles, new StringBuilder(),list,used);
  5.         for(String s:list){
  6.             System.out.print(s);
  7.         }
  8.         return list.size();
  9.     }
  10.    
  11.     public void dfs(String tiles, StringBuilder s, HashSet<String> list,boolean[] used){
  12.         if(s.length() > 0){
  13.             list.add(new String(s));
  14.         }
  15.         
  16.         for(int i=0;i<tiles.length();i++){
  17.             if(!used){
  18.                 used=true;
  19.                 s.append(tiles.charAt(i));
  20.                 dfs(tiles,s,list,used);
  21.                 s.deleteCharAt(s.length()-1);
  22.                 used=false;
  23.             }
  24.         }
  25.     }
复制代码


参考链接:
https://blog.csdn.net/yuanmxiang/article/details/68075613
https://zhuanlan.zhihu.com/p/63252392





评分

参与人数 1大米 +2 收起 理由
Tokyo职人 + 2 给你点个赞!

查看全部评分


上一篇:只以面试为目的,395 lintcode Coins in a line II这种题目是不是最好直接放弃?
下一篇:有点好奇大家刷题都是什么速度哈
🔗
337845818 2019-8-26 10:52:03 | 只看该作者
全局:
看你敲了这么多字就顶一下吧。。

分析backtrack / dfs 不写优化什么的。。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册账号
隐私提醒:
  • ☑ 禁止发布广告,拉群,贴个人联系方式:找人请去🔗同学同事飞友,拉群请去🔗拉群结伴,广告请去🔗跳蚤市场,和 🔗租房广告|找室友
  • ☑ 论坛内容在发帖 30 分钟内可以编辑,过后则不能删帖。为防止被骚扰甚至人肉,不要公开留微信等联系方式,如有需求请以论坛私信方式发送。
  • ☑ 干货版块可免费使用 🔗超级匿名:面经(美国面经、中国面经、数科面经、PM面经),抖包袱(美国、中国)和录取汇报、定位选校版
  • ☑ 查阅全站 🔗各种匿名方法

本版积分规则

>
快速回复 返回顶部 返回列表