• 首页
  • 关于
    • Pg.lost photo

      Pg.lost

    • 更多
    • LinkedIn
    • Github
    • Weibo
  • 文章
    • 列表
    • 标签
  • 项目

LeetCode:排列组合类型问题总结

11 Aug 2015

Reading time ~5 minutes

  • 写在最前
  • 基本问题:子集 Subsets
    • 问题描述
    • 问题分析
    • 代码
    • 代码要点
  • 扩展1:带重复元素的子集 Subsets II
    • 问题描述
    • 问题分析
    • 代码
    • 代码要点
  • 扩展2:全排列 Permutations
    • 问题描述
    • 代码
    • 代码要点
  • 扩展3:带重复元素的排列 Permutations II
    • 问题描述
    • 问题分析
    • 代码
    • 代码要点
  • 扩展4:组合数字 Combination Sum
    • 问题描述
    • 问题分析
    • 代码
    • 代码要点
  • 扩展5:字符串组合问题 Letter Combinations of a Phone Number
    • 问题描述
    • 问题分析
    • 代码
  • 扩展6:字符串分割之一 Palindrome Partitioning
    • 题目描述
    • 问题分析
    • 代码
    • 代码要点
  • 扩展7:字符串分割之二 Restore IP Addresses
    • 问题描述
    • 问题分析
    • 代码
    • 代码要点:
  • 总结

写在最前

最近在刷leetcode的过程中,发现排列组合类型的问题,都可以使用回朔递归的方法来解决。下面对刷题过程中遇到的排列组合类问题加以总结和整理。

基本问题:子集 Subsets

问题描述

Given a set of distinct integers, nums, return all possible subsets.

给定一个含不同整数的集合,返回其所有的子集

问题分析

这是这一类问题中的基础问题,其他排列组合的问题都可以套用本题的解题思路来解决。

代码

	class Solution {
	public:
	    vector<vector<int>> subsets(vector<int>& nums) {
	        sort(nums.begin(),nums.end());
	        vector<vector<int>> subs;
	        vector<int> sub;
	        subsetsHelper(nums,0,sub,subs);
	        return subs;
	    }
	private:
	    void subsetsHelper(vector<int>& nums,int pos,vector<int> sub,vector<vector<int>>& subs){
	        subs.push_back(sub);
	        for(int i = pos;i < nums.size();i++){
	            sub.push_back(nums[i]);
	            subsetsHelper(nums,i+1,sub,subs);
	            sub.pop_back();
	        }
	    }

	};

代码要点

  1. 因为题目中要求子集中的元素为非降序排列,因此要首先对题目给出的集合进行排序。
  2. subsetsHelper(nums,i,sub,subs)的含义是,从nums集合的第i个元素开始,计算包含sub的所有子集,并把所有的子集保存到subs中。
  3. 要想解决subsetsHelper(nums,i,sub,subs)这个问题,就必须解决它的子问题,那就是对于nums中从i开始的每一个元素,先把它保存到sub中,然后调用subsetsHelper(nums,i+1,sub,subs)。最后把这个思路转换成上述代码。

扩展1:带重复元素的子集 Subsets II

问题描述

Given a collection of integers that might contain duplicates, nums, return all possible subsets.

给定一个可能具有重复数字的列表,返回其所有可能的子集

问题分析

这个问题与Subsets类似,只要把重复的的子集排除就可以。例如:{1, 2(1), 2(2), 2(3)},规定{1, 2(1)}和{1, 2(2)}重复,{1, 2(1), 2(2)}和{1, 2(2), 2(3)}重复。从而得出规律::我们只关心取多少个2,不关心取哪几个。因此,规定必须从第一个2开始连续取(作为重复集合中的代表),如必须是{1, 2(1)}不能是{1, 2{2})。

代码

	class Solution {
	public:
	    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
	     sort(nums.begin(), nums.end());
	     vector<vector<int>> subs;
	     vector<int> sub;
	     subsetsWithDupHelper(nums, 0, sub, subs);
	     return subs;
	    }
	private:
	   void subsetsWithDupHelper(vector<int>& nums, int pos, vector<int>& sub, vector<vector<int>>& subs) {
	        subs.push_back(sub);
	        for(int i = pos; i < nums.size(); i++){
	            if(i != pos && nums[i] == nums[i-1]){
	                continue;
	            }
	            sub.push_back(nums[i]);
	            subsetsWithDupHelper(nums, i+1, sub, subs);
	            sub.pop_back();
	        }

	    }
	};

代码要点

  1. 还是首先要对集合排序,首先是因为题目要求输出的子集为非升序的,其次排序之后很容易的就把重复的子集排除。
  2. subsetsWithDupHelper(nums, i, sub, subs)的含义是,从nums的第i个元素开始,计算包含sub的所有不重复子集,并把结果存储到subs中。
  3. 如何保证计算的子集不重复?在subsetsWithDupHelper(nums, i, sub, subs)函数中,在每一层的递归中,把与前面元素重复的元素跳过。

扩展2:全排列 Permutations

Given a collection of numbers, return all possible permutations.

给定一个数字列表,返回其所有可能的排列。

问题描述

与subsets的思路类似,可以再subsets模板的基础上加以修改。

代码

	class Solution {
	public:
	    vector<vector<int>> permute(vector<int>& nums) {
	        vector<vector<int>> result;
	        vector<int> list;
	        permuteHelper(nums, list, result);
	        return result;
	    }
	private:
	    void permuteHelper(vector<int>& nums, vector<int>& list, vector<vector<int>>& result){
	        if(list.size() == nums.size()){
	            result.push_back(list);
	            return;
	        }
	        for(int i = 0; i < nums.size(); i++){
	            if(find(list.begin(),list.end(),nums[i]) != list.end()){
	                continue;
	            }
	            list.push_back(nums[i]);
	            permuteHelper(nums, list, result);
	            list.pop_back();
	        }
	    }
	};

代码要点

在subsets的基础上,做两点修改

  1. 每次递归当且仅当sub的长度与nums的长度相同时,才把sub存入到subs中。
  2. 每次递归需要把nums中所有元素遍历一遍,只有当nums中的元素在sub中包含时才跳过(排除在上上层已经参与递归的元素)

扩展3:带重复元素的排列 Permutations II

问题描述

Given a collection of numbers that might contain duplicates, return all possible unique permutations.

给出一个具有重复数字的列表,找出列表所有不同的排列

问题分析

在Permutations的基础上,把重复的组合排除,并且对已经在前一层参与递归nums的元素做排除,由于nums中有重复的元素,因此不能使用Permutations中方法,可以使用一个bool数组标识nums元素的递归状态。

代码

	class Solution {
	public:
	    vector<vector<int>> permuteUnique(vector<int>& nums) {
	        vector<vector<int>> result;
	        vector<int> list;
	        vector<bool> visited(nums.size(), false);
	        sort(nums.begin(),nums.end());
	        permuteUniqueHelper(nums, list, result,visited);
	        return result;
	    }
	private:
	    void permuteUniqueHelper(vector<int>& nums, vector<int>& list, vector<vector<int>>& result ,vector<bool>& visited){
	        if(list.size() == nums.size()){
	            result.push_back(list);
	            return;
	        }
	        for(int i = 0; i < nums.size(); i++){
	            if(visited[i] || (i != 0 && nums[i] == nums[i-1] && !visited[i-1])){
	                continue;
	            }
	            visited[i] = true;
	            list.push_back(nums[i]);
	            permuteUniqueHelper(nums, list ,result,visited);
	            list.pop_back();
	            visited[i] = false;
	        }
	    }
	};

代码要点

  1. 排除上层已经递归的元素
  2. 排除本层中重复出现的元素

扩展4:组合数字 Combination Sum

问题描述

Given a set of candidate numbers (C) and a target number (T), find all unique combinations in C where the candidate numbers sums to T.

The same repeated number may be chosen from C unlimited number of times.

给出一组候选数字(C)和目标数字(T),找到C中所有的组合,使找出的数字和为T。

C中的数字可以无限制重复被选取。

问题分析

subsets II的变形,需要考虑给出的候选集合存在重复元素的情况。递归返回的条件以及需要跳过的情况与subsets II略有不同

代码

	class Solution {
	public:
	    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
	        vector<vector<int>> result;
	        vector<int> list;
	        sort(candidates.begin(), candidates.end());
	        combinationSumHelper(candidates, target, 0,list, result);
	        return result;
	    }
	private:
	    void combinationSumHelper(vector<int>& candidates, int target, int start,vector<int>& list,vector<vector<int>>& result){
	        if (target == 0) {
	            result.push_back(list);
	            return;
	        }
	        for (int i = start; i < candidates.size(); i++) {
	            if (candidates[i] > target){
	                break;
	            }
	            if (i !=start && candidates[i-1] == candidates[i]) {
	                continue;
	            }
	            list.push_back(candidates[i]);
	            combinationSumHelper(candidates, target - candidates[i], i, list, result);
	            list.pop_back();
	        }
	    }
	};

代码要点

  1. combinationSumHelper(candidates, target, i,list, result)的含义:从candidates的第i个元素开始,选择包含list的并且满足子集的和为target的所有非重复子集,并且把找到的集合存到result中。
  2. 递归返回的条件为target为0。

扩展5:字符串组合问题 Letter Combinations of a Phone Number

问题描述

Given a digit string, return all possible letter combinations that the number could represent.

A mapping of digit to letters (just like on the telephone buttons) is given below.

问题分析

第一层递归,digits中第一个数字对应的字符集;第二层递归,包含第一层选中的字符,对digits的第二个数字做第二层递归。

代码

	class Solution {
	public:
	    vector<string> letterCombinations(string digits) {
	        vector<string> result;
	        if(digits.size() == 0){
	            return result;
	        }
	        string list;
	        vector<string> map = {"", "" , "abc", "def", "ghi", "jkl", "mno", "pqrs","tuv","wxyz"};
	        letterCombinationsHelper(digits, map, 0,list, result);
	        return result;
	    }
	    void letterCombinationsHelper(string& digits, vector<string>& map,int index, string list, vector<string>& result) {
	        if (index == digits.size()) {
	            result.push_back(list);
	            return;
	        }
	        for(int i = 0; i < map[digits[index]-'0'].size(); i++) {
	            list.push_back(map[digits[index]-'0'][i]);
	            letterCombinationsHelper(digits, map, index+1,list, result);
	            list.pop_back();
	        }
	    }
	};

###代码要点

  1. C++中vector的初始化不能用[]的形式
  2. 各种C++中字符串的操作

扩展6:字符串分割之一 Palindrome Partitioning

题目描述

Given a string s, partition s such that every substring of the partition is a palindrome.

Return all possible palindrome partitioning of s.

给定一个字符串s,将s分割成一些子串,使每个子串都是回文串。

返回s所有可能的回文串分割方案。

问题分析

第一层递归为s的以首字母开头的所有连续子串,第二层递归为上一层递归后一个字符开的所有连续子串。如果子串不为回文,则停止子递归。递归返回的条件为走到s的最后一个字符。

代码

	class Solution {
	public:
	    vector<vector<string>> partition(string s) {
	        vector<vector<string>> result;
	        vector<string> list;
	        partitionHelper(s, 0, list, result);
	        return result;
	    }
	private:
	    bool isPalindrome(const string& s) {
	        int begin = 0;
	        int end = s.size() - 1;
	        while (begin < end) {
	            if(s[begin] != s[end]) {
	                return false;
	            }
	            begin++;
	            end--;
	        }
	        return true;
	    }
	    void partitionHelper(string& s, int start, vector<string>& list, vector<vector<string>>& result) {
	        if(start == s.size()){
	            result.push_back(list);
	            return;
	        }
	        for(int i = start; i < s.size(); i++){
	            string prefix = s.substr(start,i - start + 1);
	            if(!isPalindrome(prefix)) {
	                continue;
	            }
	            list.push_back(prefix);
	            partitionHelper(s, i+1, list, result);
	            list.pop_back();
	        }
	    }
	};

代码要点

1.回文字符串的判断

扩展7:字符串分割之二 Restore IP Addresses

问题描述

Given a string containing only digits, restore it by returning all possible valid IP address combinations.

问题分析

基本同上一题类似,递归返回条件为分割了四个子串,如果递归到了s的末尾,就把当前分割存储到result中。

代码

	class Solution {
	public:
	    vector<string> restoreIpAddresses(string s) {
	        vector<string> result;
	        vector<string>  list;
	        restoreIpAddressesHelper(s, 0, list, result);
	        return result;
	    }
	private:
	    bool isValid(string s) {
	        if (s[0] == '0') {
	            return s == "0";
	        }
	        int strValue = stoi(s);
	        return strValue >= 0 && strValue <= 255;
	    }
	    void restoreIpAddressesHelper(string& s, int start, vector<string>& list, vector<string>& result) {
	        if(list.size() == 4) {
	            if(start != s.size()) {
	                return;
	            }
	            string listCombine;
	            for(int i = 0; i < list.size(); i++){
	                listCombine = listCombine + list[i] + ".";
	            }
	            listCombine = listCombine.substr(0,listCombine.size()-1);
	            result.push_back(listCombine);
	            return;
	        }
	        for(int i = start; i < s.size() && i < start + 4; i++) {
	            string prefix = s.substr(start,i - start + 1);
	            if(isValid(prefix)){
	                list.push_back(prefix);
	                restoreIpAddressesHelper(s, i+1, list, result);
	                list.pop_back();
	            }

	        }
	    }

	};

代码要点:

1.字符串的操作

总结

排列组合模板的使用范围

  1. 几乎所有的搜索问题 根据具体题目要求进行改动
  2. 什么时候输出
  3. 哪些情况需要跳过
  4. 上一层的递归和下一层递归的关系


leetcode Share Tweet +1