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

本文深入探讨了排列和组合的概念,包括它们的含义、特点和应用。针对字符串的排列问题,介绍了通用解法和避免重复的策略。通过示例解释了如何设计程序输出字符串的所有排列,提供了解决重复问题的思路。同时推荐了一系列排列组合算法的学习资源。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1算法思想

排列组合

1.1含义

排列:

含义:从元素列表中取出指定个数的元素进行排序

公式:从n个不同元素中取出m个元素的排列个数=n!/(n-m)!

组合:

含义:从元素列表中取出指定个数的元素,不排序。

公式:从n个不同元素中取出m个元素的组合个数=n!/(m! * (n-m)!)

1.2特点

1.3应用

字符串的排列:需要使用回溯(成功输出结果,设置变量,递归,清除对变量的设置)

子集生成: 第一次子集=<a>,第二次子集=<a, ab, b>,第三次子集=<a, ab, b, ac, abc, bc, c>

观察发现:当前子集结果= 上一次子集结果 + 上一次子集结果中每个元素加上当前迭代元素 + 当前迭代元素

 

1.4通用解法

排列组合通用解法:

1 排列组合需要使用回溯算法

2 子集生成可以使用迭代方法

 

1.5经典例题讲解

设计一个程序,当输入一个字符串时,要求输出这个字符串的所有排列。

例如输入字符串abc,要求输出由字符a、b、c所能排列出来的所有字符串:

abc,acb,bac,bca,cba,cab。

字符串中元素可能重复。

分析:

固定第一个字符a,对后面的字符bc进行全排列

然后交换第一个字符与其后面的字符,即交换ab,然后固定第一个字符b,

对后面的两个字符ac进行全排列

再次交换a与b(因为刚才破坏了字符串顺序)

交换ac,固定此时第一个字符c,对ab进行全排列

 

实现:

设定一个变量start表示起始字符

设定一个变量i表示后续交换的位置,i的取值从start 到 len(字符串)  - 1

此时交换的字符索引

start不断累加,表示不断固定新的位置,当start的值为len(字符串) - 1,

说明完成

 

解决重复问题:

如果前面的字符串中从[start, i)的字符串中有和当前

待交换重复的元素,则必定带来重复的全排列,

所以解决方法是:

如果string[start, i)字符串中有字符与string[i]

相同则不交换

 

代码如下:

def swap(charList, start, i):

    size = len(charList)

    if start >= size or i >= size or start < 0 or i < 0:

        return

    tmp = charList[start]

    charList[start] = charList[i]

    charList[i] = tmp

 

def isDuplicated(charList, start, i):

    size = len(charList)

    if start >= size or i >= size or start < 0 or i < 0:

        return True

    for index in range(start, i):

        if charList[index] == charList[i]:

            return True

    return False

 

def strPermutation(charList, start):

    if not charList or start < 0:

        return

    size = len(charList)

    if start == size - 1:

        print "".join(charList)

    i = start

    while i < size:

        # 判断待交换的两个元素是相同的,就不需要交换

        if isDuplicated(charList, start, i):

            i += 1

            continue

        swap(charList, start, i)

        # 固定一个字符,求剩余字符的全排列

        strPermutation(charList, start + 1)

        # 恢复字符串顺序

        swap(charList, start, i)

        # 重新设定后续的交换字符

        i += 1

 

 

2 排列组合系列

类别-编号

题目

遁去的一

1

字符串的排列:

输入一个字符串,打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a、b、c所能排列出来的

所有字符串abc、acb、bac、bca、cab和cba

剑指offer

https://blog.youkuaiyun.com/qingyuanluofeng/article/details/39138385

参见经典例题解析

2

排列问题:

有n个元素,编号为1,2,…,n,用一个具有n个元素的数组A来 存放所生成的排列,然后输出它们

算法设计与分析

https://blog.youkuaiyun.com/qingyuanluofeng/article/details/47189193

参见经典例题解析

3

电话号码对应英语单词

电话的号码盘一般可以用于输入字母。如用2可以输入A,B,C,用3可以输入D,E,F等。

对于号码5869872,可以依次输出其代表的所有字母组合。例如:JTMWTPA,JTMWTPB...

1您是否可以根据这样的对应关系设计一个程序,尽可能快地从这些字母组合中找到一个有意义的单词来表述一个电话号码呢?如:可以用单词"computer"

来描述号码26678837

2对于一个电话号码,是否可以用一个单词来代表呢?怎样才是最快的方法呢?显然,肯定不是所有的电话号码都能够对应到单词上去。但是根据问题1的解答,思路

比较清晰。

 

输入:

2

4 2

输出:

GA

GB

GC

HA

HB

HC

IA

IB

IC

编程之美

https://blog.youkuaiyun.com/qingyuanluofeng/article/details/47187539

分析:

除了0,1之外,其他数字上最少都有3个字符,其中7,9上有4个字符,我们假设0,1输出的是空字符。

首先,将问题简单化。若电话号码只有1位数,比如说4,那么其代表的“单词”为G、H、I,据此可以画出一颗排列树。

若电话号码升级到两位数,比如42,分两步走。从左到右,在选择一个第一位数字代表的字符的基础上,遍历第二位数字代表的字符,直到遍历完第一位数字代表的

所有字符。

                                                            42

                            4                     G                     H                          I

                            2             A       B       C       A     B       C       A          B             C

通过遍历这棵排列数所有叶子结点而得到的所有路径的集合,即为42所能代表的所有单词的集合。

如何遍历得到电话号码所能代表的单词集合?

问题1解法1:直接循环法

将各个数字所能代表的字符存储在一个二维数组中,其中假设0,1所代表的字符为空字符,并将各个数字所能代表的字符总数记录于另一个数组中:

问题1的解法2:递归的方法:

可以将每层的for循环看成一个递归函数的调用

问题2的解法1:

把该电话对应的字符全部统计出来,然后匹配字典,判断是否有答案。

问题2的解法2:

如果查询次数较多,可以把字典里面所有单词转换为数字,存到文件中,使之成为一本数字字典,然后对这个电话号码查表得到结果。

 

关键:

1 char g_numToWord[10][10] = //牛逼,用二维数组来建立数字到字符串的映射

{

       "",

       "",

       "ABC",

2 int g_numToWordNum[10] = {0,0,3,3,3,3,3,4,3,4};//将各个数字所能代表的字符总数记录于另一个数组中

3    int k = n -1 ; //确保每次从后向前遍历

              while(k >= 0)

              {

                     if(answer[k] < g_numToWordNum[number[k]] - 1)//这个是用来保证最多answer[k] = g... - 1,

                     {

                            answer[k]++;//累加位置,为遍历当前字母下一位做准备

                            break;//跳出的目的是为了进行下一轮打印

                     }

                     else

                     {

                            answer[k] = 0;//如果已经达到当前数字打印的最后一位,那么重新归零,k--会对当前数字前面的一个数字进行打印

                            k--;

                     }

              }

              if(k < 0)//说明所有的数字全部打印结束

              {

                     break;

4 void telephoneToWord_recursion(int* number,int* answer,int n,int iPos)//递推的结果参见刘汝佳关于全排列,素数环的算法,用iPos

5           for(answer[iPos] = 0 ; answer[iPos] < g_numToWordNum[number[iPos] ] ; answer[iPos]++)//关键:递归这里先打印的是后面的再打印前面的

              {

                     telephoneToWord_recursion(number,answer,n,iPos+1);

                    

代码:

const int MAXSIZE = 100;

char g_numToWord[10][10] = //牛逼,用二维数组来建立数字到字符串的映射

{

       "",

       "",

       "ABC",

       "DEF",

       "GHI",

       "JKL",

       "MNO",

       "PQRS",

       "TUV",

       "WXYZ"

};

 

int g_numToWordNum[10] = {0,0,3,3,3,3,3,4,3,4};//将各个数字所能代表的字符总数记录于另一个数组中

 

//递推的结果参见刘汝佳关于全排列,素数环的算法,用iPos

void telephoneToWord_recursion(int* number,int* answer,int n,int iPos)

{

       if(iPos == n)//递归出口

       {

              for(int i = 0 ; i < n ; i++)

              {

                     printf("%c",g_numToWord[ number[i] ][ answer[i] ]);

              }

              printf("\n");

       }

       else

       {

           //关键:递归这里先打印的是后面的再打印前面的

              for(answer[iPos] = 0 ; answer[iPos] < g_numToWordNum[number[iPos] ] ; answer[iPos]++)

              {

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值