现在大学男生宿舍和3年前有什么不一样?答案是:以前他们都玩Dota,而现在很多人在玩LOL。
作为一个职业宅男,LOL也成为笔者的一部分了,而笔者最喜欢的英雄除了暴力小萝莉安妮外,还有一个就是卡牌大师——一切尽在卡牌中。
问题来了:一副扑克牌除去大小王,在剩下52张牌中,随意抽出一张牌,用尽可能快尽可能少的空间求出这张牌的点数(不要求花色)。
本问题来自《编程之美》中的1.1.5:快速找出故障机器,大家觉得我写的不好,可以去看看原著。
解法一:
对于学了多年编程的读者,不难想出一个方法,用一个数组,初始化为0,之后53张牌从头到尾扫一遍,每次点数的数组位置+1,最后扫一遍数组,3次的就是被抽出牌的点数了。
这样,我们得到了答案,时间复杂度是O(n),空间复杂度是O(k),k是数的最大值。
那么,我们能不能让复杂度降低呢?
解法二:
首先,我们不难证出,求出抽出牌点数的算法至少要将牌组扫一遍,所以时间复杂度O(n)不可能降低了,那么让我们看看空间复杂度。
还记的数据结构课学的一种查找方法么?没错,即使哈希表。
建立哈希表,之后每次存入数据,如果同一位置出现两次,那么这个位置肯定不是抽出的排了,所以该位置可以释放给别的数据用了。
所以,在空间上,最好情况是O(1),最坏情况依旧是O(k)。
解法三:
有没有比较稳定的空间复杂度呢?
让我们想想我们还学过什么。
在C++,算法、数据结构、离散数学、组成原理、数电、微型计算机等课程中,我们都学过一个东西,但是老师们都没有强调过,那就是位运算。
位运算是一种很神奇的运算方式,要比普通的四则运算快很多,而在位运算中有个XOR/抑或。
A XOR A = 0, A XOR 0 = A,而且满足结合律和交换律;
而正常牌出现是偶数次,而只有被抽出的牌是奇数次,所以,将所有数XOR一遍,结果就出来,而且空间复杂度是O(1)
解法四:
让我们看看有没有别方法。
首先,这是一副牌,我们知道什么信息呢?
所有52张牌的点数我们是知道的,那么它们的和也就知道了,如果用总和减去53张牌的点数,结果就是抽出的牌咯。
追问环节:
如果我抽出两张牌怎么办呢?
显然前三种方法可以解决,但XOR方法明显出现的局限性,让我们看看第四种方法怎么办。
设这两张牌是x和y,那么根据第四种方法我们能得到x+y,显然这个解释不确定的,而解一个二元方程,我们还需要另一个方程。
那么,我们知道总和确定,那么总乘积也知道的,于是xy的值也就知道了。(如果乘积很大,我们可以用平方和来做。)
于是这个二元方程组就联立好了。
继续追问环节:
如果是抽出了K张牌怎么办。
这道题没有标准答案,虽然我们可以用第四种方法求解方程组(貌似有一种特殊的行列式是第一行是0次幂,第二行1次幂,第三行2次幂....我忘了叫什么了....),显然这个方法不是很好,这时候hash是最好的选择了,即容易实现,而且能节省空间。