最近有空闲着就刷点Leetcode题目做一做,发现有些题目还是挺有意思,挺tricky的。下面这三道题,每一道题初看起来都是so easy!但是再细分析起来,它们都有些小技巧,不能走寻常思路,而是要针对性的运行一些小技巧。
一、图像旋转问题
初看起来,图像旋转其实是图像几何变换中一个非常常规的操作。其实就是根据“线性代数”中的方法,将原像素矩阵乘以一个旋转变换矩阵就可以实现图像的任意角度旋转。这一点可以参考我的著作《数字图像处理原理与实践(MATLAB版)》中的“3.6 图像的旋转”。当然,这是一种通用的算法,它可以实现图像的任意角度旋转。但是本题中特别点明“in-place”,下面是维基百科给出的解释:
In computer science, an in-place algorithm is an algorithm which transforms input using a data structure with a small amount of extra storage space. The input is usually overwritten by the output as the algorithm executes.
说白了,就是要求空间占用最小。所以通用的做法就行不通了。仔细观察一个像素矩阵旋转90度,不难发现其中的规律:结果矩阵其实是原矩阵沿水平轴做镜像翻转,再沿主对角线翻转。例如
11 12 13 14 23 24 25 26 23 19 15 11
15 16 17 18 19 20 21 22 24 20 16 12
19 20 21 22 → 15 16 17 18 → 25 21 17 13
23 24 25 26 11 12 13 14 26 22 18 14
最后给出示例代码如下。
class Solution { public: void swap(int& a, int& b) { int tmp; tmp = b; b = a; a = tmp; } void rotate(vector<vector<int> >& matrix) { if (matrix.empty()) return; int n = matrix.size(), i = 0, tmp; for (; i < n/2; ++i) { for (int j = 0; j < n; ++j) swap(matrix[i][j], matrix[n-1-i][j]) ; } i = 0; for (; i < n; ++i) { for (int j = 0; j < i; ++j) swap(matrix[i][j], matrix[j][i]); } } };
二、2的幂次问题
这道题考察的是位操作。不妨来观察一下2的次方数的二进制写法的特点:
1 2 4 8 16 ....
1 10 100 1000 10000 ....
发现规律了吗?易见,如果一个数是2的次方数的话,那么它的二进数必然是最高位为1,其它都为0。所以你可以
每次判断最低位是否为1,然后向右移位,最后统计1的个数即可判断是否是2的次方数。但我们给出一种更简便的方法,只需一行代码就可以达成:如果将原数减1的话,则最高位会降一位,其余为0的位现在都为变为1,那么我们把两数相与,就会得到0,用这个性质也能来解题。
下面是示例代码。
class Solution { public: bool isPowerOfTwo(int n) { return (n > 0) && (!(n & (n - 1))); } };
三、主元素问题
这道题没有复杂度要求,所以如果仅仅是解决问题来说,这道题的解题方法不下三种。最最简单的,你或许会想到:遍历数组中的每一个元素,然后用另外一张Table来记录每一个不同元素出现的次数,然后从这张表中找到想要的结果。现在这种方法的时间复杂度是O(n),但空间复杂度也是O(n)。能否设计一种线性时间复杂度的算法,而不占用额外的空间呢?Boyer和Moore在下面这部著作中提出了一种非常有效的算法——Linear Time Majority Vote Algorithm.
MJRTY - A Fast Majority Vote Algorithm, with R.S. Boyer. In R.S. Boyer (ed.), Automated Reasoning: Essays in Honor of Woody Bledsoe, Automated Reasoning Series, Kluwer Academic Publishers, Dordrecht, The Netherlands, 1991, pp. 105-117.
Boyer和Moore合作的另外一个著名算法就是大名鼎鼎的 BM 模式匹配算法。尽管KMP名声也很大,但现实中使用的更多的模式匹配算法都是基于BM的演进算法。
二人给出的Vote Algorithm描述如下:
Sweep down the sequence starting at the pointer position shown above.
When sweeping, maintain a pair consisting of a current candidate and a counter. Initially, the current candidate is unknown and the counter is 0.
When we move the pointer forward over an element e:
When we are done, the current candidate is the majority element, if there is a majority.
简单点讲,就是按顺序扫描一个串,最开始计数器置为0,并保存一个current candidate,当遇到一个相同的元素,计数器就+1,遇到不同的元素,计数器就-1。如果计数器为0了,就将current candidate置为当前所描到的元素e。根据这个规则当扫描完成后,所得之结果就是Majority Element. 例如
Step1:
A A A C C B B C C C B C C
^
?:0
Step2:
A A A C C B B C C C B C C
^
A:1
Step3:
A A A C C B B C C C B C C
^
A:2
Step4:
A A A C C B B C C C B C C
^
A:3
Step5:
A A A C C B B C C C B C C
^
A:2
Step6:
A A A C C B B C C C B C C
^
A:1
Step7:
A A A C C B B C C C B C C
^
?:0
Step8:
A A A C C B B C C C B C C
^
B:1
(我们省略了后续的步骤)继续下去,最终就会得到结果C。
当然,该算法有效的前提是,Majority Element确实存在。
下面是示例代码。
class Solution { public: int majorityElement(vector<int>& nums) { int elem = 0; int count = 0; for(int i = 0; i < nums.size(); i++) { if(count == 0) { elem = nums[i]; count = 1; } else { if(elem == nums[i]) count++; else count--; } } return elem; } };