题目描述
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。
NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。
初始代码
class Solution { public: int minNumberInRotateArray(vector<int> rotateArray) { } };
解法1
好不夸张的说, 一开始看到这个题, 一直没想明白这个非递减有个什么用. 非递减的对立面是递减, 也就是说, 形如{9, 5, 4, 2, 1}
这般的数组, 而只要不是这样的数组, 都可以称之为非递减, 即数组{2, 5, 3, 6, 1, 7}
也可称为非递减. 所以, 这不就是普普通通的随机数组吗? 搞不懂题目为什么要把这作为一个条件特别提出来.
既然是随机数组, 也没什么算法好讲. 只能用暴力求解硬刚咯.
就是一个简单的O(n)复杂度的求数组最小值的遍历操作, 俗称为"打擂算法". 遍历过程中, 碰到的已知的最小元素就可以坐上"擂主"的宝座, 但保不准下一次比较时这个"擂主"就被PK下来了.
下面是代码实现:
class Solution { public: int minNumberInRotateArray(vector<int> rotateArray) { if(rotateArray.empty()) { return 0; } int min = rotateArray[0]; for(int i=1; i<rotateArray.size(); i++) { if(rotateArray[i] < min) { min = rotateArray[i]; } } return min; } };
解法2
要么是出这个题的人没脑子, 要么就是我自己还没有完全理解题意. 通过查看其它题解, 算是理解题干中的"非递减排序数组的一个旋转"意味着什么了.
即, 首先有一个数组, 它已经排好序了, 而且是按照非递减的顺序进行排序的(虽然我更倾向于将这类序列说成是非单调递增序列). 然后, 这个排好序的数组被做了一次旋转操作后, 才作为函数的参数被传进来.
弄清楚这一点, 就好说了, 因为这个数组还是挺特殊的.
如果采用二分法来查找该数组中的最小元素的话, 首先我们会取到数组的中间元素或者说中间元素的下标, 记为mid. 然后我们要拿mid处的元素和另一个元素
进行比较, 根据比较的结果来确定下一步是在左半边数组里接着找, 还是在右半边数组里接着找.
这个另一个元素
我们可以取当前数组的最右侧元素.
来做一次模拟就清楚了:
如果当前mid位置元素大于最右端的元素, 比方说当前数组为
{3, 4, 1, 2}
, mid处元素为4, 最右边元素为2,4 > 2
. 根据旋转前的数组的非递减特性, 可以判断最小数字在mid右侧.注意: 当数组元素个数为偶数时, 我们取最中间两个下标的较小的那一个为mid, 取较大的那一个当然也是可行的, 倒是取较大的一个作为mid后其它地方和取较小的那个为mid有没有区别, 需不需要微调, 则需要另外考虑一下了, 后面开一小节来讨论.
如果当前mid位置元素小于等于最右端元素, 比方说当前数组为
{4, 1, 2, 3}
或者{4, 1, 2, 2, 2}
, 同样根据旋转前数组的非递减特性, 可以判断最小数字在mid的左侧(包括mid在内).
由上面的思路, 可以实现二分法查找最小元素的算法了, 如下:
class Solution { public: int minNumberInRotateArray(vector<int> rotateArray) { if(rotateArray.empty()) return 0; if(rotateArray.size() == 1) return rotateArray[0]; int mid = (rotateArray.size() - 1) / 2; if(rotateArray.at(mid) > rotateArray.at(rotateArray.size() - 1)) { vector<int> tmp(rotateArray.begin()+mid+1, rotateArray.end()); return minNumberInRotateArray(tmp); } else { vector<int> tmp(rotateArray.begin(), rotateArray.begin()+mid+1); return minNumberInRotateArray(tmp); } } };
关于数组长度为偶数时, mid取值的问题
上面的解法2, mid在数组长度为偶数时, 取的是中间两个下标的较小者. 如果取较大者为mid会如何呢?
同样先以{3, 4, 1, 2}
为例, 现在mid处的元素值为1, 数组最右端元素为2, 1 < 2
, 因此, 按照上面的思路, 应该认为最小数字应该从mid左边的数组元素中去找(包括mid位置的元素), 而实际上也正是如此.
随后以{2, 3, 4, 1}
为例, 现在mid处元素值为4, 大于数组最右端元素1, 按照上面的思路, 接下来应该从mid右边的数组元素中找最小数字, 而实际上这也是没错的.
是否可以就此下结论说: 在数组长度为偶数时, mid无论是取中间两个下标的任何一个值, 对后续的操作都是没有影响的, 可以使用同一套代码了呢?
修改后使用自测数据{3, 4, 5, 1, 2}
测了一遍, 出现了内存超限的问题. 心算了一下, 在数组只含有{1, 2}
两个元素的时候, 如果按上面的算法, 2 <= 2
, 因此下一步从mid所处位置元素左边继续找(包括mid处元素在内), 会发现, 下一步的数组还是{1, 2}
, 就这样, 陷入死循环退不出了...
要避免这种死循环的出现, 可以在函数开头添加一个对数组长度为2时的判断: 当数组只有两个元素时, 直接返回其中的较小值即可.
下面时通过的代码:
class Solution { public: int minNumberInRotateArray(vector<int> rotateArray) { if(rotateArray.empty()) return 0; if(rotateArray.size() == 1) return rotateArray[0]; if(rotateArray.size() == 2) { return rotateArray[0]>rotateArray[1] ? rotateArray[1] : rotateArray[0]; } int mid = rotateArray.size() / 2; if(rotateArray.at(mid) > rotateArray.at(rotateArray.size() - 1)) { vector<int> tmp(rotateArray.begin()+mid+1, rotateArray.end()); return minNumberInRotateArray(tmp); } else { vector<int> tmp(rotateArray.begin(), rotateArray.begin()+mid+1); return minNumberInRotateArray(tmp); } } };