zoukankan      html  css  js  c++  java
  • 【LeetCode & 剑指offer刷题】回溯法与暴力枚举法题1:排列与组合

    【LeetCode & 剑指offer 刷题笔记】目录(持续更新中...)

    排列与组合

    说明:排列组合方法很多,不限于文中的这些方法,可以在网上多看些解法,选择几种自己比较欣赏的解法。
    1 Permutations I
    Given a collection of distinct integers, return all possible permutations.
    Example:
    Permutations I问题
    Input: [1,2,3]
    Output:
    [
    [1,2,3],
    [1,3,2],
    [2,1,3],
    [2,3,1],
    [3,1,2],
    [3,2,1]
    ]
     
    Permutations II问题
    Input: [1,1,2]
    Output:
    [
    [1,1,2],
    [1,2,1],
    [2,1,1]
    ]
     
    /*问题: next_permutation ??单个元素如果大于9还适用吗??(有时间在讨论)
    找下一个排列数,这里将排列位上的各位构成数字,下一种排列,其排列数大于输入排列数(如果输入的数为降序,已经最大,则约定下一个排列为升序排列,回到最小排列数),其最接近
    要得到某个排列数下一个最接近的排列数
     
    方法:具体见note
        
        
        最小的排列数为增序排列,最大的排列数为降序排列,中间的为乱序,
    (1) 先找第一个分割数pivot,其满足小于后一个相邻数,且最靠后(可以从后往前扫描)
    (2) 再到后面找一个刚好大于此分割数的数changenum(从后往前扫描)
    (3) 交换pivot与changenum
    (4) 反序pivot后面的序列
    例:
    6   8   7   4   3   2
    [6] 8   [7] 4   3   2  选中pivot和changenum
    [7] 8   [6] 4   3   2  交换
    7   [8  6   4   3   2] 
    7   [2  3   4   6   8] 反序pivot+1~end的序列
    */
    //排列与组合问题示例:字符串的排列
    //返回一个数组所有可能的排列结果(数组中所有元素不同 方法一也适用于Permutations II
    //可以看成回溯法,也可以看成暴力枚举法
    //方法一:偷懒法 stlnext_permutation函数
    //O(n!), O(1)
    #include <algorithm>
    class Solution
    {
    public:
        vector<vector<int>> permute(vector<int>& nums)
        {
            vector<vector<int>> result;
            sort(nums.begin(), nums.end()); //排列成增序
           
            do
            {
                result.push_back(nums);
            }while(next_permutation(nums.begin(), nums.end()));
            //若新排列按字典序大于旧者则为 true 。若抵达最后重排并重置范围为首个排列则为 false
            return result;
        }
    };
     
    //方法二:自己实现 next_permutation
    #include <algorithm>
    class Solution
    {
    public:
        vector<vector<int>> permute(vector<int>& nums)
        {
            vector<vector<int>> result;
            sort(nums.begin(), nums.end()); //排列成增序
           
            do
            {
                result.push_back(nums);
            }while(nextPermutation(nums)); //与系统默认的函数参数不同
            //若新排列按字典序大于旧者则为 true 。若抵达最后重排并重置范围为首个排列则为 false
            return result;
        }
        public:
                    //O(n),O(1)
        bool nextPermutation(vector<int>& nums)
        {
            if(nums.empty() || nums.size()==1) return false; //异常情况处理
           
            int pivot = -1; //初始化枢轴
            for(int i = nums.size()-1; i>=1; i--) //从后往前扫描,找到分割数
            {
                if(nums[i-1] < nums[i])
                {
                    pivot = i-1;
                    break;
                }
            }
            if(pivot == -1) //说明排列数已经最大,不存在分割数
            {
                reverse(nums.begin(), nums.end());
                return false; //排列数已经最大时,返回false
            }
           
            int change = pivot; //初始化changenum位置
            for(int i = nums.size()-1; i>=0; i--) //从后往前扫描,找changenum
            {
                if(nums[i] > nums[pivot])
                {
                    change = i;
                    break;
                }
            }
           
            swap(nums[pivot], nums[change]); //交换
            reverse(nums.begin()+pivot+1, nums.end()); //反序(注意这里是begin()+pivot+1,对应索引为pivot+1的位置)
           
            return true; //存在下一个排列数    
           
        }
    };
     
    /*
    方法三:递归法
    */
    #include <algorithm>
    class Solution
    {
    public:
        vector<vector<int> > permute(vector<int>& num)
        {
                         
            vector<vector<int>> result;
            if(num.empty()) return result;
            vector<int> path; //中间结果
            sort(num.begin(), num.end()); //sort之后递归函数才能按全排列顺序排列(好的初始顺序)
            dfs(num, path, result);
            return result;
        }
    private:
        void dfs(vector<int>& num, vector<int>& path, vector<vector<int>>& result)
        {
            if(path.size() == num.size())
            {
                result.push_back(path);
                return;
            }
          
            for(int a:num) //用于产生递归树某一层的多个分支,if语句来约束分支
            {
                if(find(path.begin(), path.end(), a) == path.end()) //如果在当前路径上没有元素a,则将该元素push到路径
                {
                    path.push_back(a);
                    dfs(num, path, result); //产生递归树的深度
                    path.pop_back(); //回溯,腾出空间,供一个分支push元素
                }
            }
        }
      
    };
     
     
    2 Permutations II
    Given a collection of numbers that might contain duplicates, return all possible unique permutations.
    Example:
    Input: [1,1,2]
    Output:
    [
    [1,1,2],
    [1,2,1],
    [2,1,1]
    ]
     
    //返回一个数组所有可能的排列结果(数组中存在重复元素)
    /*
    方法一:用next_permutation ,同问题Permutations 1
    */
    /*
    方法二:dfs
    用一个map来统计各个元素出现的次数,通过这个容器约束各路径
    与问题Permutations 1不同的是现在用计数器来约束,而之前用输入的num数组来约束选取(递归函数for循环部分)
    */
    #include <map>
    class Solution
    {
    public:
        vector<vector<int>> permuteUnique(vector<int>& nums)
        {
           
            vector<vector<int>> result;
            if(nums.empty()) return result;
           
            vector<int> path;
            map<int,int> counter;
            // sort(str.begin(), str.end()); //上面用了map,故这里无需先sort
            for(int a:nums) counter[a]++; //统计nums中各数出现的次数没有key的时候会自动创建
           
            dfs(nums,  counter, path, result);//递归
            return result;
        }
    private:
        void dfs(vector<int>& nums, map<int,int>& counter, vector<int>& path, vector<vector<int>>& result)
        {
            if(path.size() == nums.size()) //到达树的末尾,将单路径数组push到结果向量中
            {
                result.push_back(path);
                return;
            }
          
            for(auto& p:counter) //for循环带来的是树宽度方向的延伸,即产生同一层的多个分支
            {
                if(p.second>0) //如果该元素没有被取完(某个元素可能会出现多次)
                {
                    path.push_back(p.first);
                    p.second--; //已经取了这个元素,统计数减一
                    dfs(nums, counter, path, result); //继续往深度方向延伸
                    path.pop_back();  //回溯,给其他分支腾空间!!
                    p.second++;
                }
            }
        }
    };
     
    3 Combinations
    Given two integers n and k, return all possible combinations of k numbers out of 1 ... n.
    Example:
    Input: n = 4, k = 2
    Output:
    [
      [2,4],
      [3,4],
      [2,3],
      [1,2],
      [1,3],
      [1,4],
    ]
     
    //产生所有可能组合(给nk,返回从1~nk个数的组合)
    /*
    递归法:画出递归树(数的组合数),然后设计递归函数
    举例,1~4k=3
    为了避免重复,后面的数都必须必前面的大)
                    dfs(3) push and return
            dfs(2)  dfs(4) push and return
           
    dfs(1)  dfs(3)  dfs(4) push and return
            dfs(4)  return(递归子程序结束)
    dfs(2)....
    dfs(3)...
    dfs(4)...
    */
    class Solution
    {
    public:
        vector<vector<int>> combine(int n, int k)
        {
            vector<vector<int>> result;
            vector<int> path;
           
            if(n<=0 || k<=0) return result;
           
            dfs(n, k, 1, path, result);
            return result;
        }
    private:
        //start为每个父结点的子结点开始的数,子结点取nums[start~end]
        //到树的末尾后pushresult
        void dfs(int n, int k, int start, vector<int>& path, vector<vector<int>>& result)
        {
            if(path.size() == k //步数达到k后push路径,步数达不到k的,会运行到for循环,函数末尾而结束(递归到start = n时就会结束)
            {
                result.push_back(path);
                return; //递归出口之一
            }
           
            for(int i = start; i<=n; i++) //产生父结点的多个子结点
            {
                path.push_back(i);
                dfs(n, k, i+1, path, result); //深度方向和宽度方向都是以i~end扩展,而记录深度变化,通过step
                为了避免重复,后面的数都必须必前面的大)某分支下一个step的start = i+1
                path.pop_back(); //腾出空间
            }
        }
    };
     
  • 相关阅读:
    数据库索引概念与优化
    数据库查询效率分析
    C语言结构体与C++结构体之间的差异
    判断一个序列是否为栈的弹出序列
    C语言中的结构体
    C++ STL 中的 std::sort()
    Spring注入值到静态变量
    层次遍历二叉树
    计算二叉树的大小
    计算二叉树的高度
  • 原文地址:https://www.cnblogs.com/wikiwen/p/10229436.html
Copyright © 2011-2022 走看看