剑指Offer_面试题28_字符串的排列 实现了字符排列、去重的问题,由此可以扩展出很多问题如组合问题。
扩展1
字符组合问题。如输入三个字符a、b、c,则他们的组合有a、b、c、ab、ac、bc、abc。
分析:与排列解法一致,把问题分解。假设我们要在10个字符中取3个字符组合。
那么我们把这10个字符分为两部分:第一个字符和其余9个字符。如果组合里包含第一个字符,那么就在剩余9个字符中选取2个字符组合;如果不包含第一个字符,那么就从剩余9个字符中选取3个字符组合。于是乎得到问题的分解:
n个字符中取m个组合(1<= m <= n)可以分解成两个子问题:
①求n-1个字符中的m-1个字符组合
②求n-1个字符中的m个字符组合
这两个子问题都可以用递归的方式解决。
递归解决:
///剑指offer递归解决思路:
//字符str取number个组合
void combination(char *str, int number, vector<char>& cRes, vector<string>& res)
{
if (str == NULL) return;
if (number == 0)
{
//将字符vector连起来转换为string
string tempStr(cRes.size(), ' ');
for (size_t i = 0; i < cRes.size(); ++i)
tempStr[i] = cRes[i];
res.push_back(tempStr);
return;
}
if (*str == '\0') return; //这个必须在后
cRes.push_back(*str);
combination(str+1, number-1, cRes, res);
cRes.pop_back(); //相当于还原初始状态
combination(str+1, number, cRes, res);
}
vector<string> combination(char *str)
{
vector<string> res;
int len = strlen(str);
if (len)
{
for (int i = 1; i <= len; ++i)
{
vector<char> cRes;
combination(str, i, cRes, res);
}
}
return res;
}
int main()
{
char *str2 = "abc";
vector<string> res = combination(str2);
for (auto s : res)
cout << s << endl;
getchar();
return 0;
}
结果:
说一说我之前的笨办法解法。我用了排列算法排出“10”的所有组合,比如10个字符选3个字符组合,那我先构造一个只含有10的字符串“1110000000”一共10位,含有3个1,进行去重全排列,然后下标对应字符串的下标,是1则取str的对应下标字符,0则不取,组成长度为3的字符串作为结果。只能是代码很复杂,真要碰到面试,可能写不完,只能大概说说想法。
扩展2
输入一个含有8个数字的数组,判断有没有可能把这8个数字分别放到正方体的8个顶点上,使得正方体的3组相对的面上的4个顶点的和都相等。
如上图,其实试求是否8个数的排列能满足a1+a2+a3+a4 = a5+a6+a7+a8且a1+a5+a7+a3 = a2+a6+a8+a4且a1+a2+a6+a5=a3+a4+a8+a7
#include <stdio.h>
#include <stdlib.h>
#include <vector>
#include <unordered_set>
#include <iostream>
using namespace std;
//判断排列是否满足
bool isSuccessful(vector<int> perm)
{
if (perm.size() != 8) return false;
bool b1 = ((perm[0] + perm[1] + perm[2] + perm[3]) ==
(perm[4] + perm[5] + perm[6] + perm[7]));
bool b2 = ((perm[0] + perm[2] + perm[4] + perm[6]) ==
(perm[1] + perm[3] + perm[5] + perm[7]));
bool b3 = ((perm[0] + perm[1] + perm[4] + perm[5]) ==
(perm[2] + perm[3] + perm[6] + perm[7]));
return b1 && b2 && b3;
}
bool permutation(vector<int> input, int k, vector<int> &res)
{
if (k == input.size())
{
if (isSuccessful(input))
{
res = input;
return true;
}
}
unordered_set<int> record;
for (int i = k; i < input.size(); ++i)
{
if (record.find(input[i]) == record.end())
{
record.insert(input[i]);
int temp = input[i];
input[i] = input[k];
input[k] = temp;
if (permutation(input, k + 1, res))
return true;
temp = input[i];
input[i] = input[k];
input[k] = temp;
}
}
return false;
}
bool permutation(vector<int> input, vector<int>& res)
{
if (input.size() != 8) return false;
bool bRes = false;
bRes = permutation(input, 0, res);
return bRes;
}
int main()
{
vector<int> input = {1,2,3,4,5,6,7,8};
vector<int> res;
if (permutation(input, res))
{
for (int i = 0; i < 8; ++i)
printf("a%d\t", i+1);
cout << endl;
for (auto i : res)
cout << i << "\t";
cout << endl;
}
else
cout << "no match" << endl;
getchar();
return 0;
}
边排列边判断,有符合的结果则返回。所有排列结果不必保存,只需要保存符合答案的那一个。
扩展3 八皇后问题
分析:8x8棋盘,8个皇后必定位于不同的行,每一行一个皇后,那么可变的就是每一行皇后的列号,而列肯定不相同,所以可以用一个数组array[i],i从0到7代表行,array[i]代表第i+1行皇后的列号,范围从0-7,只需要做0、1、2、3、4、5、6、7的全排列,然后判断全排列那一个符合要求,其实这里已经满足不同行不同列,那么只需要判断任意两个行的皇后他们不再一个对角线上,对角线判断 i - j == array[i] - array[j] 和 i - j == array[j]-array[i](正反对角)即可:
n皇后代码:
#include <stdio.h>
#include <stdlib.h>
#include <vector>
#include <iostream>
using namespace std;
void perm(vector<int>input, int k, vector<vector<int> >&res)
{
if (k == input.size())
{
res.push_back(input);
}
for (int i = k; i < input.size(); ++i)
{
int temp = input[i];
input[i] = input[k];
input[k] = temp;
perm(input, k + 1, res);
temp = input[i];
input[i] = input[k];
input[k] = temp;
}
}
vector<vector<int> > perm(vector<int> input)
{
vector<vector<int> >res;
if (!input.empty())
{
perm(input, 0, res);
}
return res;
}
//判断列号组合是不是皇后问题的解(判断在不在对角线)
bool isMatched(vector<int> array)
{
if (array.empty()) return false;
int len = array.size();
for (int i = 0; i < len; ++i)
{
for (int j = i + 1; j < len; ++j)
{
//判断在一条对角上
if ((j - i) == (array[j] - array[i]) ||
(j - i) == (array[i] - array[j]))
return false;
}
}
return true;
}
int Queens(unsigned int numbers, vector<vector<int> > &res)
{
if (numbers == 0) return -1;
vector<int>input(numbers, 0);
vector<vector<int> > ans;
vector<vector<int> > arrays;
for (unsigned int i = 0; i < numbers; ++i)
input[i] = i;
arrays = perm(input);
int count = 0;
for (auto v : arrays)
{
if (isMatched(v))
{
ans.push_back(v);
++count;
}
}
res = ans;
return count;
}
int main()
{
vector<vector<int> > res;
int n = Queens(8, res);
if (n)
{
for (auto v : res)
{
for (auto i : v)
cout << i << " ";
cout << endl;
}
cout << "共" << n << "个" << endl;
}
getchar();
return 0;
}
总结:剑指offer28字符串排列这一题涉及很多知识,扩展出很多东西,值得花时间去整理。包括排列组合、分解问题的思路、递归的思路、八皇后等等。