`
844604778
  • 浏览: 550406 次
文章分类
社区版块
存档分类
最新评论

[互联网面试笔试汇总C/C++-11] 字符串全排列和组合算法

 
阅读更多
全排列在笔试面试中很热门,因为它难度适中,既可以考察递归实现,又能进一步考察非递归的实现,便于区分出考生的水平。所以在百度和迅雷的校园招聘以及程序员和软件设计师的考试中都考到了,因此本文对全排列作下总结帮助大家更好的学习和理解。对本文有任何补充之处,欢迎大家指出。
首先来看看题目是如何要求的(百度迅雷校招笔试题)。
一、字符串的排列
用C++写一个函数, 如 Foo(const char *str), 打印出 str 的全排列,
如 abc 的全排列: abc, acb, bca, dac, cab, cba

一、全排列的递归实现

为方便起见,用123来示例下。123的全排列有123、132、213、231、312、321这六种。首先考虑213和321这二个数是如何得出的。显然这二个都是123中的1与后面两数交换得到的。然后可以将123的第二个数和每三个数交换得到132。同理可以根据213和321来得231和312。因此可以知道——全排列就是从第一个数字起每个数分别与它后面的数字交换。找到这个规律后,递归的代码就很容易写出来了:

  1. #include<iostream>
  2. usingnamespacestd;
  3. #include<assert.h>
  4. voidPermutation(char*pStr,char*pBegin)
  5. {
  6. assert(pStr&&pBegin);
  7. if(*pBegin=='\0')
  8. printf("%s\n",pStr);
  9. else
  10. {
  11. for(char*pCh=pBegin;*pCh!='\0';pCh++)
  12. {
  13. swap(*pBegin,*pCh);
  14. Permutation(pStr,pBegin+1);
  15. swap(*pBegin,*pCh);
  16. }
  17. }
  18. }
  19. intmain(void)
  20. {
  21. charstr[]="abc";
  22. Permutation(str,str);
  23. return0;
  24. }

另外一种写法:

  1. //k表示当前选取到第几个数,m表示共有多少个数
  2. voidPermutation(char*pStr,intk,intm)
  3. {
  4. assert(pStr);
  5. if(k==m)
  6. {
  7. staticintnum=1;//局部静态变量,用来统计全排列的个数
  8. printf("第%d个排列\t%s\n",num++,pStr);
  9. }
  10. else
  11. {
  12. for(inti=k;i<=m;i++)
  13. {
  14. swap(*(pStr+k),*(pStr+i));
  15. Permutation(pStr,k+1,m);
  16. swap(*(pStr+k),*(pStr+i));
  17. }
  18. }
  19. }
  20. intmain(void)
  21. {
  22. charstr[]="abc";
  23. Permutation(str,0,strlen(str)-1);
  24. return0;
  25. }
如果字符串中有重复字符的话,上面的那个方法肯定不会符合要求的,因此现在要想办法来去掉重复的数列。
二、去掉重复的全排列的递归实现
由于全排列就是从第一个数字起每个数分别与它后面的数字交换。我们先尝试加个这样的判断——如果一个数与后面的数字相同那么这二个数就不交换了。如122,第一个数与后面交换得212、221。然后122中第二数就不用与第三个数交换了,但对212,它第二个数与第三个数是不相同的,交换之后得到221。与由122中第一个数与第三个数交换所得的221重复了。所以这个方法不行。

换种思维,对122,第一个数1与第二个数2交换得到212,然后考虑第一个数1与第三个数2交换,此时由于第三个数等于第二个数,所以第一个数不再与第三个数交换。再考虑212,它的第二个数与第三个数交换可以得到解决221。此时全排列生成完毕。
这样我们也得到了在全排列中去掉重复的规则——去重的全排列就是从第一个数字起每个数分别与它后面非重复出现的数字交换。下面给出完整代码:

  1. #include<iostream>
  2. usingnamespacestd;
  3. #include<assert.h>
  4. //在[nBegin,nEnd)区间中是否有字符与下标为pEnd的字符相等
  5. boolIsSwap(char*pBegin,char*pEnd)
  6. {
  7. char*p;
  8. for(p=pBegin;p<pEnd;p++)
  9. {
  10. if(*p==*pEnd)
  11. returnfalse;
  12. }
  13. returntrue;
  14. }
  15. voidPermutation(char*pStr,char*pBegin)
  16. {
  17. assert(pStr);
  18. if(*pBegin=='\0')
  19. {
  20. staticintnum=1;//局部静态变量,用来统计全排列的个数
  21. printf("第%d个排列\t%s\n",num++,pStr);
  22. }
  23. else
  24. {
  25. for(char*pCh=pBegin;*pCh!='\0';pCh++)//第pBegin个数分别与它后面的数字交换就能得到新的排列
  26. {
  27. if(IsSwap(pBegin,pCh))
  28. {
  29. swap(*pBegin,*pCh);
  30. Permutation(pStr,pBegin+1);
  31. swap(*pBegin,*pCh);
  32. }
  33. }
  34. }
  35. }
  36. intmain(void)
  37. {
  38. charstr[]="baa";
  39. Permutation(str,str);
  40. return0;
  41. }
OK,到现在我们已经能熟练写出递归的方法了,并且考虑了字符串中的重复数据可能引发的重复数列问题。那么如何使用非递归的方法来得到全排列了?

三、全排列的非递归实现
要考虑全排列的非递归实现,先来考虑如何计算字符串的下一个排列。如"1234"的下一个排列就是"1243"。只要对字符串反复求出下一个排列,全排列的也就迎刃而解了。
如何计算字符串的下一个排列了?来考虑"926520"这个字符串,我们从后向前找第一双相邻的递增数字,"20"、"52"都是非递增的,"26 "即满足要求,称前一个数字2为替换数,替换数的下标称为替换点,再从后面找一个比替换数大的最小数(这个数必然存在),0、2都不行,5可以,将5和2交换得到"956220",然后再将替换点后的字符串"6220"颠倒即得到"950226"。
对于像“4321”这种已经是最“大”的排列,采用STL中的处理方法,将字符串整个颠倒得到最“小”的排列"1234"并返回false。

这样,只要一个循环再加上计算字符串下一个排列的函数就可以轻松的实现非递归的全排列算法。按上面思路并参考STL中的实现源码,不难写成一份质量较高的代码。值得注意的是在循环前要对字符串排序下,可以自己写快速排序的代码(请参阅《白话经典算法之六 快速排序 快速搞定》),也可以直接使用VC库中的快速排序函数(请参阅《使用VC库函数中的快速排序函数》)。下面列出完整代码:

  1. #include<iostream>
  2. #include<algorithm>
  3. #include<cstring>
  4. usingnamespacestd;
  5. #include<assert.h>
  6. //反转区间
  7. voidReverse(char*pBegin,char*pEnd)
  8. {
  9. while(pBegin<pEnd)
  10. swap(*pBegin++,*pEnd--);
  11. }
  12. //下一个排列
  13. boolNext_permutation(chara[])
  14. {
  15. assert(a);
  16. char*p,*q,*pFind;
  17. char*pEnd=a+strlen(a)-1;
  18. if(a==pEnd)
  19. returnfalse;
  20. p=pEnd;
  21. while(p!=a)
  22. {
  23. q=p;
  24. p--;
  25. if(*p<*q)//找降序的相邻2数,前一个数即替换数
  26. {
  27. //从后向前找比替换点大的第一个数
  28. pFind=pEnd;
  29. while(*pFind<*p)
  30. --pFind;
  31. swap(*p,*pFind);
  32. //替换点后的数全部反转
  33. Reverse(q,pEnd);
  34. returntrue;
  35. }
  36. }
  37. Reverse(a,pEnd);//如果没有下一个排列,全部反转后返回false
  38. returnfalse;
  39. }
  40. intcmp(constvoid*a,constvoid*b)
  41. {
  42. returnint(*(char*)a-*(char*)b);
  43. }
  44. intmain(void)
  45. {
  46. charstr[]="bac";
  47. intnum=1;
  48. qsort(str,strlen(str),sizeof(char),cmp);
  49. do
  50. {
  51. printf("第%d个排列\t%s\n",num++,str);
  52. }while(Next_permutation(str));
  53. return0;
  54. }
至此我们已经运用了递归与非递归的方法解决了全排列问题,总结一下就是:
1、全排列就是从第一个数字起每个数分别与它后面的数字交换。
2、去重的全排列就是从第一个数字起每个数分别与它后面非重复出现的数字交换。
3、全排列的非递归就是由后向前找替换数和替换点,然后由后向前找第一个比替换数大的数与替换数交换,最后颠倒替换点后的所有数据。

二、字符串的组合

题目:输入一个字符串,输出该字符串中字符的所有组合。举个例子,如果输入abc,它的组合有a、b、c、ab、ac、bc、abc。

上面我们详细讨论了如何用递归的思路求字符串的排列。同样,本题也可以用递归的思路来求字符串的组合。

假设我们想在长度为n的字符串中求m个字符的组合。我们先从头扫描字符串的第一个字符。针对第一个字符,我们有两种选择:第一是把这个字符放到组合中去,接下来我们需要在剩下的n-1个字符中选取m-1个字符;第二是不把这个字符放到组合中去,接下来我们需要在剩下的n-1个字符中选择m个字符。这两种选择都很容易用递归实现。下面是这种思路的参考代码:
  1. #include<iostream>
  2. #include<vector>
  3. #include<cstring>
  4. usingnamespacestd;
  5. #include<assert.h>
  6. voidCombination(char*string,intnumber,vector<char>&result);
  7. voidCombination(char*string)
  8. {
  9. assert(string!=NULL);
  10. vector<char>result;
  11. inti,length=strlen(string);
  12. for(i=1;i<=length;++i)
  13. Combination(string,i,result);
  14. }
  15. voidCombination(char*string,intnumber,vector<char>&result)
  16. {
  17. assert(string!=NULL);
  18. if(number==0)
  19. {
  20. staticintnum=1;
  21. printf("第%d个组合\t",num++);
  22. vector<char>::iteratoriter=result.begin();
  23. for(;iter!=result.end();++iter)
  24. printf("%c",*iter);
  25. printf("\n");
  26. return;
  27. }
  28. if(*string=='\0')
  29. return;
  30. result.push_back(*string);
  31. Combination(string+1,number-1,result);
  32. result.pop_back();
  33. Combination(string+1,number,result);
  34. }
  35. intmain(void)
  36. {
  37. charstr[]="abc";
  38. Combination(str);
  39. return0;
  40. }

由于组合可以是1个字符的组合,2个字符的字符……一直到n个字符的组合,因此在函数void Combination(char* string),我们需要一个for循环。另外,我们用一个vector来存放选择放进组合里的字符。
方法二:用位运算来实现求组合

  1. #include<iostream>
  2. usingnamespacestd;
  3. inta[]={1,3,5,4,6};
  4. charstr[]="abcde";
  5. voidprint_subset(intn,ints)
  6. {
  7. printf("{");
  8. for(inti=0;i<n;++i)
  9. {
  10. if(s&(1<<i))//判断s的二进制中哪些位为1,即代表取某一位
  11. printf("%c",str[i]);//或者a[i]
  12. }
  13. printf("}\n");
  14. }
  15. voidsubset(intn)
  16. {
  17. for(inti=0;i<(1<<n);++i)
  18. {
  19. print_subset(n,i);
  20. }
  21. }
  22. intmain(void)
  23. {
  24. subset(5);
  25. return0;
  26. }

字符串全排列扩展----八皇后问题
题目:在8×8的国际象棋上摆放八个皇后,使其不能相互攻击,即任意两个皇后不得处在同一行、同一列或者同一对角斜线上。下图中的每个黑色格子表示一个皇后,这就是一种符合条件的摆放方法。请求出总共有多少种摆法。


这就是有名的八皇后问题。解决这个问题通常需要用递归,而递归对编程能力的要求比较高。因此有不少面试官青睐这个题目,用来考察应聘者的分析复杂问题的能力以及编程的能力。

由于八个皇后的任意两个不能处在同一行,那么这肯定是每一个皇后占据一行。于是我们可以定义一个数组ColumnIndex[8],数组中第i个数字表示位于第i行的皇后的列号。先把ColumnIndex的八个数字分别用0-7初始化,接下来我们要做的事情就是对数组ColumnIndex做全排列。由于我们是用不同的数字初始化数组中的数字,因此任意两个皇后肯定不同列。我们只需要判断得到的每一个排列对应的八个皇后是不是在同一对角斜线上,也就是数组的两个下标i和j,是不是i-j==ColumnIndex[i]-Column[j]或者j-i==ColumnIndex[i]-ColumnIndex[j]。

关于排列的详细讨论,详见上面的讲解。
接下来就是写代码了。思路想清楚之后,编码并不是很难的事情。下面是一段参考代码:

  1. #include<iostream>
  2. usingnamespacestd;
  3. intg_number=0;
  4. voidPermutation(int*,int,int);
  5. voidPrint(int*,int);
  6. voidEightQueen()
  7. {
  8. constintqueens=8;
  9. intColumnIndex[queens];
  10. for(inti=0;i<queens;++i)
  11. ColumnIndex[i]=i;//初始化
  12. Permutation(ColumnIndex,queens,0);
  13. }
  14. boolCheck(intColumnIndex[],intlength)
  15. {
  16. inti,j;
  17. for(i=0;i<length;++i)
  18. {
  19. for(j=i+1;j<length;++j)
  20. {
  21. if(i-j==ColumnIndex[i]-ColumnIndex[j]||j-i==ColumnIndex[i]-ColumnIndex[j])//在正、副对角线上
  22. returnfalse;
  23. }
  24. }
  25. returntrue;
  26. }
  27. voidPermutation(intColumnIndex[],intlength,intindex)
  28. {
  29. if(index==length)
  30. {
  31. if(Check(ColumnIndex,length))//检测棋盘当前的状态是否合法
  32. {
  33. ++g_number;
  34. Print(ColumnIndex,length);
  35. }
  36. }
  37. else
  38. {
  39. for(inti=index;i<length;++i)//全排列
  40. {
  41. swap(ColumnIndex[index],ColumnIndex[i]);
  42. Permutation(ColumnIndex,length,index+1);
  43. swap(ColumnIndex[index],ColumnIndex[i]);
  44. }
  45. }
  46. }
  47. voidPrint(intColumnIndex[],intlength)
  48. {
  49. printf("%d\n",g_number);
  50. for(inti=0;i<length;++i)
  51. printf("%d",ColumnIndex[i]);
  52. printf("\n");
  53. }
  54. intmain(void)
  55. {
  56. EightQueen();
  57. return0;
  58. }
转载:http://zhedahht.blog.163.co

题目:输入两个整数n和m,从数列1,2,3...n中随意取几个数,使其和等于m,要求列出所有的组合。

  1. #include<iostream>
  2. #include<list>
  3. usingnamespacestd;
  4. list<int>list1;
  5. voidfind_factor(intsum,intn)
  6. {
  7. //递归出口
  8. if(n<=0||sum<=0)
  9. return;
  10. //输出找到的数
  11. if(sum==n)
  12. {
  13. list1.reverse();
  14. for(list<int>::iteratoriter=list1.begin();iter!=list1.end();iter++)
  15. cout<<*iter<<"+";
  16. cout<<n<<endl;
  17. list1.reverse();
  18. }
  19. list1.push_front(n);
  20. find_factor(sum-n,n-1);//n放在里面
  21. list1.pop_front();
  22. find_factor(sum,n-1);//n不放在里面
  23. }
  24. intmain(void)
  25. {
  26. intsum,n;
  27. cin>>sum>>n;
  28. cout<<"所有可能的序列,如下:"<<endl;
  29. find_factor(sum,n);
  30. return0;
  31. }
分享到:
评论

相关推荐

    C/C++笔试题(附答案,华为面试题系列)

    (1)不调用C++/C 的字符串库函数,请编写函数 strcat 答: VC源码: char * __cdecl strcat (char * dst, const char * src) { char * cp = dst; while( *cp ) cp++; /* find end of dst */ while( *cp++ = *src++ ...

    互联网校招题库资料笔试面试真题具体面试问题回答技巧腾讯阿里培训资料.zip

    互联网校招题库资料笔试面试真题具体面试问题回答技巧腾讯阿里培训资料: C++面试题笔试题 C语言 IQ智力面试题笔试题 JAVA笔试面试资料 NET面试题笔试题 web开发 数据库面试题笔试题 算法 数据结构 计算机基础 计算机...

    vcmianshi.rar_c++ thread_算法笔试面试_算法面试题_马戏团

    C++程序员面试、笔试经常遇到的一些算法示例集 pdf,相关内容:字符串匹配的KMP算法,括号匹配检测、求一个数组的最长递减字序列、一些数字题求解,输出一个字符串的所有组合,马戏团表演问题、Thread.sleep 与obj....

    C++ 笔试题 google 微软 华为 索尼 中兴 大唐 各种C++笔试题目

    C++笔试题 Sony笔试题 几道题目及自做答案 北电 普天C++笔试题 ...雅虎笔试题(字符串操作) C语言最长平台算法 华为3COM C语言题库 将两个无序数组合并为有序链表 上海聚力传媒技术有限公司官方VC笔试题解答

    C/C++面试题目及解答.doc

    用C/C++语言写一函数完成该算法,给出复杂度 &lt;br&gt;6.对序列1、1、2、3、5、8、13。。。。 是Fab..数列 2、3、5、13...是Fab..质数数列,因为他们与自己前面的Fab...数列都互质 给出k,返回第k小的...

    免费下载:C语言难点分析整理.doc

    16. C语言字符串函数大全 68 17. C语言宏定义技巧 89 18. C语言实现动态数组 100 19. C语言笔试-运算符和表达式 104 20. C语言编程准则之稳定篇 107 21. C语言编程常见问题分析 108 22. C语言编程易犯毛病集合 112 ...

    c语言难点分析整理,C语言

    16. C语言字符串函数大全 68 17. C语言宏定义技巧 89 18. C语言实现动态数组 100 19. C语言笔试-运算符和表达式 104 20. C语言编程准则之稳定篇 107 21. C语言编程常见问题分析 108 22. C语言编程易犯毛病集合 112 ...

    高级C语言 C 语言编程要点

    16. C语言字符串函数大全 68 17. C语言宏定义技巧 89 18. C语言实现动态数组 100 19. C语言笔试-运算符和表达式 104 20. C语言编程准则之稳定篇 107 21. C语言编程常见问题分析 108 22. C语言编程易犯毛病集合 112 ...

    史上最强的C语言资料

    16. C语言字符串函数大全 68 17. C语言宏定义技巧 89 18. C语言实现动态数组 100 19. C语言笔试-运算符和表达式 104 20. C语言编程准则之稳定篇 107 21. C语言编程常见问题分析 108 22. C语言编程易犯毛病集合 112 ...

    高级C语言详解

    16. C语言字符串函数大全 68 17. C语言宏定义技巧 89 18. C语言实现动态数组 100 19. C语言笔试-运算符和表达式 104 20. C语言编程准则之稳定篇 107 21. C语言编程常见问题分析 108 22. C语言编程易犯毛病集合 112 ...

    C语言难点分析整理

    16. C语言字符串函数大全 68 17. C语言宏定义技巧 89 18. C语言实现动态数组 100 19. C语言笔试-运算符和表达式 104 20. C语言编程准则之稳定篇 107 21. C语言编程常见问题分析 108 22. C语言编程易犯毛病集合 112 ...

    C++组织及解析JSON格式

    C++组织及解析JSON格式字符串,使用方便,操作简单。

    C语言难点分析整理.doc

    16. C语言字符串函数大全 68 17. C语言宏定义技巧 89 18. C语言实现动态数组 100 19. C语言笔试-运算符和表达式 104 20. C语言编程准则之稳定篇 107 21. C语言编程常见问题分析 108 22. C语言编程易犯毛病集合...

    高级进阶c语言教程..doc

    16. C语言字符串函数大全 68 17. C语言宏定义技巧 89 18. C语言实现动态数组 100 19. C语言笔试-运算符和表达式 104 20. C语言编程准则之稳定篇 107 21. C语言编程常见问题分析 108 22. C语言编程易犯毛病集合 112 ...

    给定一个字符串,求出其最长的重复子串(腾讯2011年10月15日校招笔试)

    腾讯2011年10月15日校招笔试算法编程题

    找工作面试算法笔记

    个人找工作期间总结的相关面试...有清晰的归类,包括:排序、链表、字符串、队列和栈、二叉树、二叉搜索树等。【注】:个人总结、每个算法都是本人编写、编译、调试通过的。找完工作,贡献出来,以便帮助更多小伙伴!

    c++笔试题(无答案)

    用C/C++语言写一函数完成该算法,给出复杂度 6.对序列1、1、2、3、5、8、13。。。。 是Fab..数列 2、3、5、13...是Fab..质数数列,因为他们与自己前面的Fab...数列都互质 给出k,返回第k小的Fab..质数 7.101个...

    IT常见面试笔试题含答案

    不调用C++/C 的字符串库函数, 请编写函数 strcpy 答案: char *strcpy(char *strDest, const char *strSrc) { if ( strDest == NULL || strSrc == NULL) return NULL ; if ( strDest == strSrc) return strDest ; ...

    常考的算法题,C&C++

    不错的搜集的算法题,大家一起交流。 1.将字符串逆序,要求时间空间最省。 2.字符串求子串,String substring(String orginal,int i,int j)

    leetcode分类-DataStructures-and-Algorithms:算法虐我千百遍,主要放leetcode,力扣,牛客网算法练习

    字符串 bachelorOnlineExam:一些本科生阶段实习生笔试题 masterOnlineExam:一些研究生阶段实习生笔试题 networkMeasurement:牛客网算法练习题 swordRefersToOffer:剑指offer算法题 C++ algorithms algorithm-...

Global site tag (gtag.js) - Google Analytics