面试题28:字符串的排列
来源:互联网 发布:c 棋牌游戏源码 编辑:程序博客网 时间:2024/05/16 17:28
面试题28:字符串的排列
题目描述:
输入一个字符串,返回该字符串中字符的全排列。
题目分析:
再来看这道题时,分外“眼红”,当年此题竟然没有做出来。
这道题,可以说用分治的思想,将字符串分成两个子问题,子问题1太小不需要再求解,只需要求解子问题2。当然,也可以理解为递归,《算法导论》讲动态规划章节时,有以这种朴素递归为入口来叙述。
1. 递归解法
思路分析1:
递归,将字符串分为两部分,第一个字符和剩余部分的字符串,先确定第一个字符求出对应的全排列,再将第一个字符逐个和后面的字符交换。
该题的关键是:
- 递归结束的条件;
- 如果有重复的字符,如何处理,避免交换;
class Solution {public: void Permu(vector<string> &res, string &str, int left) { /* string字符串对象也会加'\0',作为字符串的结尾 */ if (str[left] == '\0') { res.push_back(str); return; } for (int mov = left; str[mov] != '\0'; mov ++) { /* * if判断去重,如果是重复字符则不需要交换, * 但第一个字符还是要交换的。 */ if (mov == left || str[mov] != str[left]) { swap(str[mov], str[left]); Permu(res, str, left + 1); swap(str[mov], str[left]); } }} vector<string> Permutation(string str) { vector<string> res; if (str.length() < 1) return res; Permu(res, str, 0); /* 牛客网要求按字典序输出,所以多加一个排序 */ sort(res.begin(), res.end()); return res; }};
2. 非递归解法
思路分析2:
按照字典序排序的思想逐一生成所有序列。
求 p[1…n] 的下一个排列的描述:
- 求i = max{j | p[j] < p[j + 1]}(找最后一个正序)
- 求j = max{k | p[i] < p[k]}(找最后一个大于p[i]的p[k])
(找大于p[i]的是因为小于p[i]的序列都已经求过) - 交换 p[i] 与 p[j]得到 p[1] … p[i-1] p[j] p[i+1] … p[j-1] p[i] p[j+1] … p[n]
- 反转 p[j] 后面的数得到 p[1] … p[i-1] p[j] p[n] … p[j+1] p[i] p[j-1] … p[i+1]
给一个具体的例子来解释上述4步:
求排列8347521的下一个排列:
- 求i,第一个正序47,i = 3
- 求j,最后一个大于4的是5,j = 5
- 交换p[i]和p[j]得,8357421
- 将p[j]后面的数反转得,8351247
代码如下:
class Solution {public: vector<string> Permutation(string str) { vector<string> res; int len = str.size(); if (len == 0) return res; sort(str.begin(), str.end()); int i, j; while (1) { res.push_back(str); for (i = len - 2; i >= 0; i --) { if (str[i] < str[i + 1]) { for (j = len - 1; j > i; j --) if (str[j] > str[i]) break; swap(str[i], str[j]); ReverseStr(str, i + 1, len - 1); break; } } if (i == -1) break; } return res; } void ReverseStr(string &str, int left, int right) { while (left < right) { swap(str[left], str[right]); ++ left; -- right; } }};
非递归实现参考:
http://liangjiabin.com/blog/2015/04/leetcode-permutation-generation-algorithm.html (有改动)
3. 该题的引申—康托公式
忘了是XXX公司的笔试题,给定一个字符串S,再给一个字符串T,求字符串T是字符串S是按照字典序输出的第几个字符串。
常规解法:依次求出字符串S的字典序的序列,统计个数,分别和字符串T比较,如果相等,返回统计的个数。
应用康托公式就会简单许多。
但是康托公式展开不能有重复值。
3.1 康托公式和康托展开
康托公式:
X=a[n](n-1)!+a[n-1](n-2)!+…+a[i]*(i-1)!+…+a[1]*0!
其中,a[i]为整数,并且0<= a[i] < i, 1<=i<=n。
康托展开和逆展开:
康托展开是一个全排列到一个自然数的双射,常用于构建哈希表时的空间压缩。 康托展开的实质是计算当前排列在所有由小到大全排列中的顺序,因此是可逆的。
举例解释康托展开的过程:
例如,3 5 7 4 1 2 9 6 8 展开为 94。
因为X=2*8!+3*7!+4*6!+2*5!+0*4!+0*3!+2*2!+0*1!+0*0!=94.
解释:
排列的第一位是3,比3小的数有两个,以这样的数开始的排列有8!个,因此第一项为2*8!
排列的第二位是5,比5小的数有1、2、3、4,由于3已经出现,因此共有3个比5小的数,这样的排列有7!个,因此第二项为3*7!
以此类推,直至0*0!
3.2 康托展开
题目:给定字符串S=“abcdefgh”,任意给定一个字符串S的排列T,求T是S的字典序顺序的第几个排列。
思路分析:根据康托公式计算康托值,从第一位开始s[0],比s[0]小的有k个,则k * 7!,依次计算得出康托值。
代码如下:
/* 求小于t[i]的字母的个数 */int Less(string t, int index) { int res = 0; res = t[index] - 'a'; if (index == 0) { return res; } else { for (int j = 0; j < index; j ++) if (t[j] < t[index]) -- res; } return res;}int Fib(int n) { if (n == 0 || n == 1) return 1; int f0 = 1; int f1 = 1; int f2; for (int i = 2; i <= n; i ++) { f2 = f0 + f1; f0 = f1; f1 = f2; } return f2;}/* 计算康托值 */int Cantor(string t, int len) { int i; int count = 0; for (i = 0; i < len; i ++) { int val = Less(t, i) * Fib(len - i - 1); count += val; } return count;}
3.3 康托逆展开
题目:给定字符串S=“abcdefgh”和康托值n,求对应的字符串T。
思路分析:康托展开是一个双射,,自然可以通过康托值求字符串全排列中第n大的排列。
给一个例子就容易理解多了。
如n=5,x=96时:
首先用96-1得到95,说明x之前有95个排列.(将此数本身减去!)
用95去除4! 得到3余23,说明有3个数比第1位小,所以第一位是4.
用23去除3! 得到3余5,说明有3个数比第2位小,所以是4,但是4已出现过,因此是5.
用5去除2!得到2余1,类似地,这一位是3.
用1去除1!得到1余0,这一位是2.
最后一位只能是1.
所以这个数是45321.
参考:
[1] http://www.lxway.com/448528981.htm
[2] http://comzyh.com/blog/archives/92/
- 面试题28:字符串的排列
- 面试题28:字符串的排列
- 面试题28 字符串的全排列
- 面试题28:字符串的排列
- 面试题28:字符串的排列
- 面试题28:字符串的排列
- 面试题28:字符串的排列
- 面试题28:字符串的排列
- 面试题28字符串的排列
- 面试题28:字符串的排列
- 面试题28:字符串的排列
- 面试题28:字符串的排列
- 面试题28—字符串的排列
- 面试题28:字符串排列
- 程序员面试题精选(28):字符串的排列
- 程序员面试题精选100题(28)-字符串的排列
- 微软,Google面试题 (28) —— 字符串的排列
- 程序员面试题精选100题(28)-字符串的排列
- [opencv] 摄像头操作
- Win7与ubuntu双系统开机找不到ubuntu引导项问题解决
- android自定义dialog,添加了selector和shape混合使用
- 逻辑地址、线性地址、物理地址以及虚拟存储器
- 深入Java集合学习系列:TreeMap实现
- 面试题28:字符串的排列
- iOS新手入门之UIView
- Fresco(2)——fresco的基本使用
- c++ primer 第四章习题(2)
- HDOJ2087 剪花布条(kmp)
- Precision & Recall & F1
- 机器学习中的特征——特征选择的方法以及注意点
- 分布式基础学习_分布式计算系统(Map/Reduce)
- 揭秘Mysql 5.6 Index Condition Pushdown(ICP)