这个十分有趣的题目出自知乎http://www.zhihu.com/question/24905007/answer/29414497 ,排名第一的知友的答案用python简洁的给出了代码和答案,枉费我用C++ 鼓捣了半天。。。
不过也不能算是白做,还是有颇多收获的。
先准确描述一下这个问题解决思路:
这题目本质就是计算排列:
定义一个函数 permutation(n.m) :从n个数中选m个进行全排列
我们要计算的就是
1. Σ permutation(9,i) (i从1到9)
结果等于3024+15120+60480+181440+362880+362880=985824
2. 然后剔除特殊情况:
13或31出现在2之前
17或71出现在4之前
19或91出现在5之前
37或73出现在5之前
79或97出现在8之前
28或82出现在7之前
46或64出现在5之前
下面从头分析:
乍看问题,这一定是一个排列问题,但是这里存在两个容易遇到的麻烦:
1. 但我们一般实现的都是全排列,所以第一个问题就是 如何由全排列计算排列。
2. 还有一个问题,大多数人实现的全排列并没有真的实现排列,只是借助递归栈来达到输出所有排列,这个差异是很大的,我们仅仅输出是不够的,我们应该将每种记录下来,然后进行排除。
第一个问题如何解决:
我在百般尝试后,求助于STL ,发现STL 中也只有一个next_permutation(字典序全排列的下一个),并没有实现普通排列和组合。
这提示我: 既然标准库只提供一个全排列,说明排列可以很容易从全排列那里获取,事实上这一点也和我们的直觉符合,经过验证和分析:
我认为这个问题是这样的:
针对字典序的全排列,我们要求next_permutation(n,m)需要借助next_permutation(n)//全排列函数
步骤如下:
l 将容器[m,n)逆序,然后求next_permutation;
l 此时[0,m)即为next_permutation
原理也很简单,就是利用了字典序的特点,为什么可以这样做,其实是因为当我们做全排列的时候其实信息是最大的,我们自然可以从中剖析出规模更小的排列
第二个问题:
由于我自己以前实现的全排列函数就可以直接将所有结果保存在一个vector里面,因此本来是不存在这个问题的。这不是一个算法的问题,是一个设计的问题,但这个问题也可以很复杂,视全排列函数的实现而异。
先展示一下 python 代码:(来源是知乎上述网址,我对其命名进行一些改动,方便理解)
#python
from itertools import * impossible = {'13': '2', '46': '5', '79': '8', '17': '4', '28': '5', '39': '6', '19': '5', '37': '5','31': '2', '64': '5','97': '8','71': '4','82': '5', '93': '6','91': '5','73': '5'} def count_unlock_screen(): bucket = chain(*(permutations('123456789', i) for i in range(4, 10))) count = 0 for i in bucket: striter = ''.join(i) for key, value in impossible.items(): if key in striter and value not in striter[:striter.find(key)]: break else: count += 1 return count print( count_unlock_screen() )
#include<iostream> #include<string> #include<algorithm> using namespace std; bool legal(string left, string right, string mid, string &a) { string test1 = left + right; string test2 = right + left; string::size_type p1 = a.find(test1); string::size_type p2 = a.find(test2); string::size_type q = a.find(mid); if ((p1 != string::npos) && (q == string::npos)) return false; if ((p2 != string::npos) && (q == string::npos)) return false; if((p1 != string::npos) && q > p1) return false; if ((p2 != string::npos) && q > p2) return false; return true; } bool is_legal(string &a) { string::size_type p = string::npos; string one("1");string two("2");string three("3"); string four("4"); string five("5"); string six("6"); string seven("7"); string eight("8"); string nine("9"); if ((!legal(one, three, two, a)) || (!legal(one, seven, four, a)) || (!legal(one, nine, five, a)) || (!legal(three, seven, five, a)) || (!legal(three, nine, six, a)) || (!legal(seven, nine, eight, a))||(!legal(two, eight, five, a))||(!legal(four, six, five, a))) return false; else return true; }
void swap(char &index1, char &index2) { char temp = index1; index1 = index2; index2 = temp; } //全排列中的下一个 bool next_permutation(string &a) { int i = 0; int size = a.size(); for (i = size - 2; i >= 0; i--) //从size开始找到第一个满足a[i] < a[i+1]的位置i { if (a[i] < a[i + 1]) break; } if (i == -1) //寻找结束后若i=-1说明当前的a数组已经达到字典序的最大 { return true; } int j = i + 1; //j用于遍历i---n之间的元素 int min_larger = j; //min_larger存储i---n之间满足 a[min_larger] > a[j] 中的最小者,由于之前i循环中判断得知a[i+1]一定大于a[i] //因此,将min_larger初始化为i+1 while (j < size && a[j] > a[i]) { if (a[min_larger] > a[j]) min_larger = j; j++; } swap(a[i], a[min_larger]); string::iterator kb = a.begin() + i + 1; string::iterator ke = a.end(); reverse(kb, ke); return false; } //计算下一个排列数 bool next_k_permutation(string &a,int length,string &result) { reverse(a.begin() + length, a.end()); if (!next_permutation(a)) { string ss(a, 0, length); result = ss; return true; } return false; } void count_all(string&a) { int length = a.size(); int count = 0; while (length >= 4) { sort(a.begin(), a.end()); count++; string result; while (next_k_permutation(a, length,result)) { if (is_legal(result)) { count++; } } --length; } cout << count << endl; } int main() { string s = "123456789"; count_all(s); system("pause"); return 0; }
python 代码运行效率确实很快,对于如此大量的计算,几秒中之内就可以给出结果。说明python的数据结构和库 优化还是很好的,对比我自己实现的C++ 方案明显要快的多,显然不是说C++ 效率比较低,但C++ 对于字符串处理的确非常麻烦,我也没有过多的针对性优化,所以运行就比较慢了,但结果是正确的。
这个问题还是非常有趣的,但是其实还是蛮难的,尝试用数学直接手算但是放弃了,排除实在比较麻烦,还是借助计算机啦!