【LeetCode & 剑指offer刷题】回溯法与暴力枚举法题1:排列与组合

【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
// 可以看成回溯法,也可以看成暴力枚举法
// 方法一:偷懒法 stl next_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],
]
 
// 产生所有可能组合(给 n k, 返回从 1~n k 个数的组合)
/*
递归法:画出递归树(数的组合数),然后设计递归函数
举例, 1~4 k=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]
    // 到树的末尾后 push result
    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 (); // 腾出空间
        }
    }
};
 

 

posted @ 2019-01-06 17:06 wikiwen 阅读( ...) 评论( ...) 编辑 收藏
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值