目录

一、题目:字符串的排列

1.1 全排列的递归实现(不考虑有字符重复)

1.1.1 写法1

1.1.2 写法2

1.2 全排列的递归实现 (考虑有字符重复)

1.3 全排列的非递归实现

二、字符串的组合

2.1 方法1

2.2 方法2: 用位运算来实现求组合

三. 字符串全排列扩展----八皇后问题

四. 相关题目

参考


  • 首先来看看题目是如何要求的(百度迅雷校招笔试题)。
  • 同剑指offer第二版: "面试题: 38"

一、题目:字符串的排列

  • 用C++写一个函数, 如 Foo(const char *str), 打印出 str 的全排列,

    • 如 abc 的全排列: abc, acb, bca, dac, cab, cba

1.1 全排列的递归实现(不考虑有字符重复)

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

1.1.1 写法1

#include<iostream>
using namespace std;
#include<assert.h>void Permutation(char* pStr, char* pBegin)
{assert(pStr && pBegin);if(*pBegin == '\0')printf("%s\n",pStr);else{for(char* pCh = pBegin; *pCh != '\0'; pCh++){swap(*pBegin,*pCh);Permutation(pStr, pBegin+1);swap(*pBegin,*pCh);}}
}int main(void)
{char str[] = "abc";Permutation(str,str);return 0;
}

1.1.2 写法2

//k表示当前选取到第几个数的索引,m表示最后一个数的索引
void Permutation(char* pStr,int k,int m)
{assert(pStr);if(k == m){static int num = 1;  //局部静态变量,用来统计全排列的个数printf("第%d个排列\t%s\n",num++,pStr);}else{for(int i = k; i <= m; i++){swap(*(pStr+k),*(pStr+i));Permutation(pStr, k + 1 , m);swap(*(pStr+k),*(pStr+i));}}
}int main(void)
{char str[] = "abc";Permutation(str , 0 , strlen(str)-1);return 0;
}

如果字符串中有重复字符的话,上面的那个方法肯定不会符合要求的,因此现在要想办法来去掉重复的数列。

1.2 全排列的递归实现 (考虑有字符重复)

由于全排列就是从第一个数字起每个数分别与它后面的数字交换。我们先尝试加个这样的判断——如果一个数与后面的数字相同那么这二个数就不交换了。如122,第一个数与后面交换得212、221。然后122中第二数就不用与第三个数交换了,但对212,它第二个数与第三个数是不相同的,交换之后得到221。与由122中第一个数与第三个数交换所得的221重复了。所以这个方法不行。

换种思维,对122,第一个数1与第二个数2交换得到212,

然后考虑第一个数1与第三个数2交换,此时由于第三个数等于第二个数,所以第一个数不再与第三个数交换。

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

#include<iostream>
using namespace std;
#include<assert.h>//在[nBegin,nEnd)区间中是否有字符与下标为pEnd的字符相等
bool IsSwap(char* pBegin , char* pEnd)
{char *p;for(p = pBegin ; p < pEnd ; p++) // 遍历:如第二个数等于第三个数时,返回false,不交换{if(*p == *pEnd)return false;}return true;
}
void Permutation(char* pStr , char *pBegin)
{assert(pStr);if(*pBegin == '\0'){static int num = 1;  //局部静态变量,用来统计全排列的个数printf("第%d个排列\t%s\n",num++,pStr);}else{for(char *pCh = pBegin; *pCh != '\0'; pCh++)   //第pBegin个数分别与它后面的数字交换就能得到新的排列   {if(IsSwap(pBegin , pCh)){swap(*pBegin , *pCh);Permutation(pStr , pBegin + 1);swap(*pBegin , *pCh);}}}
}int main(void)
{char str[] = "baa";Permutation(str , str);return 0;
}

OK,到现在我们已经能熟练写出递归的方法了,并且考虑了字符串中的重复数据可能引发的重复数列问题。那么如何使用非递归的方法来得到全排列了?

1.3 全排列的非递归实现

要考虑全排列的非递归实现,先来考虑如何计算字符串的下一个排列。如"1234"的下一个排列就是"1243"。只要对字符串反复求出下一个排列,全排列的也就迎刃而解了。
如何计算字符串的下一个排列了?来考虑"926520"这个字符串,我们从后向前找第一双相邻的递增数字,"20"、"52"都是非递增的,"26 "即满足要求,称前一个数字2为替换数,替换数的下标称为替换点,再从后面找一个比替换数大的最小数(这个数必然存在),0、2都不行,5可以,将5和2交换得到"956220",然后再将替换点后的字符串"6220"颠倒即得到"950226"。
对于像“4321”这种已经是最“大”的排列,采用STL中的处理方法,将字符串整个颠倒得到最“小”的排列"1234"并返回false。
这样,只要一个循环再加上计算字符串下一个排列的函数就可以轻松的实现非递归的全排列算法。按上面思路并参考STL中的实现源码,不难写成一份质量较高的代码。值得注意的是在循环前要对字符串排序下,可以自己写快速排序的代码(请参阅《白话经典算法之六 快速排序 快速搞定》),也可以直接使用VC库中的快速排序函数(请参阅《使用VC库函数中的快速排序函数》)。下面列出完整代码:


#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
#include<assert.h>//反转区间
void Reverse(char* pBegin , char* pEnd)
{while(pBegin < pEnd)swap(*pBegin++ , *pEnd--);
}
//下一个排列
bool Next_permutation(char a[])
{assert(a);char *p , *q , *pFind;char *pEnd = a + strlen(a) - 1;if(a == pEnd)return false;p = pEnd;while(p != a){q = p;p--;if(*p < *q)  //找降序的相邻2数,前一个数即替换数  {//从后向前找比替换点大的第一个数pFind = pEnd;while(*pFind < *p)--pFind;swap(*p , *pFind);//替换点后的数全部反转Reverse(q , pEnd);return true;}}Reverse(a , pEnd);   //如果没有下一个排列,全部反转后返回false   return false;
}int cmp(const void *a,const void *b)
{return int(*(char *)a - *(char *)b);
}
int main(void)
{char str[] = "bac";int num = 1;qsort(str , strlen(str),sizeof(char),cmp);do{printf("第%d个排列\t%s\n",num++,str); }while(Next_permutation(str));return 0;
}

至此我们已经运用了递归与非递归的方法解决了全排列问题,总结一下就是:

  • 1、全排列就是从第一个数字起每个数分别与它后面的数字交换。
  • 2、去重的全排列就是从第一个数字起每个数分别与它后面非重复出现的数字交换。
  • 3、全排列的非递归就是由后向前找替换数和替换点,然后由后向前找第一个比替换数大的数与替换数交换,最后颠倒替换点后的所有数据。

二、字符串的组合

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

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

假设我们想在长度为n的字符串中求m个字符的组合。我们先从头扫描字符串的第一个字符。针对第一个字符,我们有两种选择:第一是把这个字符放到组合中去,接下来我们需要在剩下的n-1个字符中选取m-1个字符;第二是不把这个字符放到组合中去,接下来我们需要在剩下的n-1个字符中选择m个字符。这两种选择都很容易用递归实现。下面是这种思路的参考代码:

2.1 方法1

#include<iostream>
#include<vector>
#include<cstring>
using namespace std;
#include<assert.h>void Combination(char *string ,int number,vector<char> &result);void Combination(char *string)
{assert(string != NULL);vector<char> result;int i , length = strlen(string);for(i = 1 ; i <= length ; ++i)    // 依次选择1个字符的情况、2个字符的情况...Combination(string , i ,result);
}void Combination(char *string ,int number , vector<char> &result)
{assert(string != NULL);if(number == 0)      //返回条件1:选择数量够了时{static int num = 1;printf("第%d个组合\t",num++);vector<char>::iterator iter = result.begin();for( ; iter != result.end() ; ++iter)printf("%c",*iter);printf("\n");return ;}if(*string == '\0')   //返回条件2:到达字符串结尾时return ;result.push_back(*string);Combination(string + 1 , number - 1 , result); //若选择第1个,选择后面m-1个result.pop_back();                             //清空结果,即还原到上一个状态Combination(string + 1 , number , result);     //若不选择第1个,选择后面m个
}int main(void)
{char str[] = "abc";Combination(str);return 0;
}

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

2.2 方法2: 用位运算来实现求组合

#include<iostream>
using namespace std;int a[] = {1,3,5,4,6};
char str[] = "abcde";void print_subset(int n , int s)
{printf("{");for(int i = 0 ; i < n ; ++i){if( s&(1<<i) )         // 判断s的二进制中哪些位为1,即代表取某一位printf("%c ",str[i]);   //或者a[i]}printf("}\n");
}void subset(int n)
{for(int i= 0 ; i < (1<<n) ; ++i){print_subset(n,i);}
}int main(void)
{subset(5);return 0;
}

三. 字符串全排列扩展----八皇后问题

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

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

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

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

#include<iostream>
using namespace std;int g_number = 0;
void Permutation(int * , int  , int );
void Print(int * , int );void EightQueens( )
{const int length= 8;int ColumnIndex[length];for(int i = 0 ; i < length; ++i)ColumnIndex[i] = i;    //初始化,该方法自然满足不同行不同列Permutation(ColumnIndex , length, 0);
}bool Check(int ColumnIndex[] , int length)
{int i,j;for(i = 0 ; i < length; ++i){for(j = i + 1 ; j < length; ++j){if( i - j == ColumnIndex[i] - ColumnIndex[j] || j - i == ColumnIndex[i] - ColumnIndex[j])   //检查 是否在正、副对角线上return false;}}return true;
}
void Permutation(int ColumnIndex[] , int length , int index)
{if(index == length){if( Check(ColumnIndex , length) )   //检测棋盘当前的状态是否合法{++g_number;Print(ColumnIndex , length);}}else{for(int i = index ; i < length; ++i)   //全排列{swap(ColumnIndex[index] , ColumnIndex[i]);Permutation(ColumnIndex , length , index + 1);swap(ColumnIndex[index] , ColumnIndex[i]);}}
}void Print(int ColumnIndex[] , int length)
{printf("%d\n",g_number);for(int i = 0 ; i < length; ++i)printf("%d ",ColumnIndex[i]);printf("\n");
}int main(void)
{EightQueens();return 0;
}

四. 相关题目

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

#include <iostream>
#include <list>
using namespace std;
list<int> list1;
void find_factor(int sum,int n)
{//递归出口if(n<=0||sum<=0)return;//输出找到的数if(sum==n){list1.reverse();for(list<int>::iterator iter=list1.begin();iter!=list1.end();iter++)cout<<*iter<<"+";cout<<n<<endl;list1.reverse();}list1.push_front(n);find_factor(sum-n,n-1);//n放在里面list1.pop_front();find_factor(sum,n-1);//n不放在里面
}int main(void)
{int sum,n;cin>>sum>>n;cout<<"所有可能的序列,如下:"<<endl;find_factor(sum,n);return 0;
}

参考

  • 字符串的全排列和组合算法

算法面试题_求给定字符串的排列、组合、八皇后问题相关推荐

  1. java算法面试题_【干货】经典算法面试题代码实现-Java版

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 面试题3:二维数组中的查找 题目描述:一个二维数组,每一行从左到右递增,每一列从上到下递增.输入一个二维数组和一个整数,判断数组中是否含有整数. publ ...

  2. java 排序算法面试题_面试题: java中常见的排序算法的实现及比较

    1.冒泡排序 1.1 冒泡排序普通版 每次冒泡过程都是从数列的第一个元素开始,然后依次和剩余的元素进行比较,若小于相邻元素,则交换两者位置,同时将较大元素作为下一个比较的基准元素,继续将该元素与其相邻 ...

  3. prim算法_历时两月,终拿字节跳动offer,算法面试题分享「带答案」

    欢迎关注专栏<Java架构筑基>--专注于Java技术的研究与分享! Java架构筑基​zhuanlan.zhihu.com Java架构筑基--专注于Java技术的研究与分享! 后续文章 ...

  4. 每日算法面试题,大厂特训二十八天——第十五天(字符串)

    本文来源于算法面试题特训专栏,这里有大量专业性的算法题比如(动态规划21天,大厂特训28天等等) 欢迎大家一起学习. 链接:传送门 目录标题 导读 算法特训二十八天 面试题 点击直接资料领取 导读 肥 ...

  5. 66 道前端算法面试题附思路分析助你查漏补缺

    大家好,我是漫步. 今天来分享一篇干货,前端关于算法的分析不多,下文列举了66道前端算法面试题,希望对你有所帮助. 作者:Eno_Yao https://segmentfault.com/a/1190 ...

  6. 微软的100道算法面试题(终结版)

    前言 数据结构与算法的重要性已不言而喻,最近,我整理出十大经典排序算法.五大常用算法总结,今天特意整理出微软面试的100题,若有不足之处,欢迎指正!由于篇幅过长,前30道题目写在上一篇,大家可以进我的 ...

  7. 数据结构经典算法面试题

    转自:http://hi.baidu.com/geogre_jsj/blog/item/e4b98fd2aab5aa3611df9b92.html 由于这些题,实在太火了.所以,应广大网友建议要求,在 ...

  8. 经典算法面试题及答案

    经典算法面试题及答案  1. 时针分针重合几次 表面上有60个小格,每小格代表一分钟, 时针每分钟走1/12小格,分针每分钟走1小格,从第一次重合到第二次重合分针比时针多走一圈即60小格,所以 60/ ...

  9. [Z]谷歌(Google)算法面试题

    谷歌(Google)算法面试题 1.谷歌面试题:给定能随机生成整数 1 到 5 的函数,写出能随机生成整数 1 到 7 的函数. 回答:此题的关键是让生成的 1 到 7 的数出现概率相同. 只要我们可 ...

  10. java面笔试_java笔试手写算法面试题大全含答案

    java笔试手写算法面试题大全含答案 1.统计一篇英文文章单词个数. public class WordCounting { public static void main(String[] args ...

最新文章

  1. 查找字符位置_查找某个字符最后一次出现的位置
  2. Swift之高德地图自定义标注弹出气泡样式
  3. Java程序(类的抽象与封装)
  4. Oracle 增删改查
  5. oracle表空间大小规划,关于oracle表空间的规划方法
  6. Java Spring MVC框架搭建(一)
  7. .net core ef mysql 的使用
  8. 对C# 程序员来说现在是到目前为止最好的时代
  9. C. Minimum Grid Path(思维)
  10. “约见”面试官系列之常见面试题第十三篇之css动画效果(建议收藏)
  11. 流量运营数据产品最佳实践——美团旅行流量罗盘
  12. 第7期 Datawhale 组队学习计划
  13. PHP向服务器错误记录、文件或远程目标发送一个错误
  14. javaweb不同用户登录不同页面的页面_Java Web轻松学36 - 第二个Servlet应用租房网(1)...
  15. nginx linux 系统服务,把ngnix注册为linux服务 将Nginx设置为linux下的服务
  16. Linux下安装python27
  17. linux上实现getch()函数
  18. 从下单到享用,美团外卖背后的分布式架构设计
  19. 计算机主机的拆卸步骤,电脑主板怎么拆CPU 主板拆CPU步骤图文教程
  20. Eclipse设置UTF-8编码格式

热门文章

  1. ANTS Memory Profiler - NET内存泄漏分析工具
  2. BeautifulSoup实现博文简介与过滤恶意标签(xxs攻击)
  3. 用shell查找某目录下的最大文件
  4. matlab sparse()
  5. King's Quest - poj 1904(强连通分量+外挂输入输出)
  6. 令前端工程师追捧的一款前端开发IDE工具WebStorm
  7. JQuery简要介绍(转)
  8. C#对象的浅拷与深拷贝
  9. 序列化和反序列化(四)——序列化存储规则
  10. 再谈如何以最简单的方法将泛型为String类型的集合或String类型的数组转化为逗号间隔字符串形式