关于二分法在旋转数组中的应用

来源:互联网 发布:战舰世界藏王数据 编辑:程序博客网 时间:2024/06/06 20:20

关于旋转数组的问题在IT公司面试中是很常见的,而二分法因为其能达到O(logn)的效率而广受欢迎,网上也有不少相关的博文,本篇文章主要围绕下面两个议题讨论二分法在旋转数组中的应用。
旋转数组: 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。

1. 旋转数组中查找最小数

题目:

输入一个有序(增序)数组的一个旋转,输出旋转数组的最小元素。例如数组a {3,4,5,6,7,8,9,0,1,2} 为{0,1,2,3,4,5,6,7,8,9}的一个旋转,该数组的最小元素为0.

思路:

       可以很容易观察到,旋转之后的数组实际上可以化为为两个排序的子数组{3,4,5,6,7,8,9}和{0,1,2},并且前面的子数组元素都大于或等于后面子数组的元素,最小的元素位于后面子数组的首位,刚好是两子数组的分界线。另外,如果数组旋转0位是不变的,它也是数组的一个旋转,那么最小元素实际上就是第一个元素。

因为数组在一定程度上是有序的,因此我们可以用二分法的思路来寻找最小的元素。用两个指针分别指向数组的第一个和最后一个元素,如果数组有实际的旋转(非0位),第一个元素必定是大于或等于最后一个元素的。接着我们取数组中间的元素,通过判断中间元素位于前后哪个子数组中来得出最小元素所在的子数组,如果中间元素的值大于或等于第一个元素,那么它位于前面的子数组,最小元素应该处于该中间元素的后面,此时我们可以移动第一个指针指向该中间元素,以此来缩小查找的范围。同样的,如果中间元素位于后面的子数组,则数组中最小元素应该位于该中间元素的前面或者就是中间元素,此时可以移动第二个指针指向该中间元素,缩小查找的范围。继续新一轮的查找并更新指针,最终两个指针会指向两相邻的元素,第一个指针指向前面子数组的最后一个元素,而个第二个指针会指向后面数组的第一个元素,即为最小的那个元素,循环结束。

       有一种特殊的case需要注意,例如{1,1,0,1,1,1,1},两个子数组分别为{1,1},{0,1,1,1,1}, 中间元素和第一个、最后一个元素都相等,按照以上算法会有问题,因为中间元素和第一个元素相等,最小元素会被错判到缩小范围后的子数组{1,1,1,1}中。那么如果是这种case,我们只能顺序查找最小元素。

实现:

int minInOrder(int a[], int head, int tail){int res = a[head];for (int i = head + 1; i <= tail; i++){if (a[i] < res)res = a[i];}return res;}int minInRotateArray(int a[], int len){assert(len > 0);if (1 == len)return a[0];int head = 0;int tail = len-1;            int mid = (head + tail)/2;if (a[head] == a[tail] && a[mid] == a[head]){return minInOrder(a, head, tail); // sequential search}while (a[head] >= a[tail]){if (1 == tail - head ){return a[tail];}if (a[mid] >= a[head])head = mid;else if (a[mid] <= a[tail])tail = mid;mid = (head + tail)/2;}return a[0];}

2. 旋转数组查找给定数

题目:

输入一个有序(增序)数组的一个旋转,查找并输出给定值为x的元素位置,若查找不到则输出-1。例如数组a {3,4,5,6,7,8,9,0,1,2} 为{0,1,2,3,4,5,6,7,8,9}的一个旋转,需要查找x=8的元素,输出为6.

思路:

同样用二分法来寻找给定的x。用两个指针分别指向数组的第一个和最后一个元素,我们取中间元素,先确定中间元素是处于前后两个子数组中的哪一个,然后再通过x和中间元素、第一个以及最后一个元素的对比来确定x是处于哪个子数组,然后移动移动两指针来缩小搜索的范围。
如果中间元素处于前子数组,若x等于中间元素,则查找成功;若x大于中间元素,则x应该位于中间元素的右边,移动第一个指针指向中间元素后面的一个元素;若x小于中间元素,则需要进一步对比x和第一个指针指向的值,若相等,则查找成功;若x大,则x应该处于中间元素的左边,移动第二个指针指向中间元素的前一个元素,x应该处于中间元素的右边,移动第一个指针指向中间元素的后一个元素。中间元素处于后子数组可做类似的分析。
同样的,我们需要考虑两种特例:
        a. 旋转数组旋转0位,上面算法同样适用;
        b. {1,1,0,1,1,1,1}这种case,查找0会失败,算法不适用。类似的,拎出来作特殊处理。

实现:

int searchInOrder(int a[], int len, int x){int i = 0;for ( ; i < len; i++){return i + 1;}if (len == i)return -1;}int searchInRotateArray(int a[], int len, int x){assert(len > 0);int head = 0;                                               int tail = len - 1;int mid = (head + tail)/2;if (a[head] == a[tail] && a[mid] == a[head])return searchInOrder(a, len, x);while((head <= tail)){    if (head == tail)                                             return x == a[head] ? head + 1 : -1;int mid = (head + tail)/2;if (a[mid] >= a[head]){if (x == a[mid])return mid + 1;else if (x > a[mid])head = mid + 1;else{if (x >= a[head])tail = mid - 1;else head = mid + 1;}}else {if (x == a[mid])return mid + 1;else if (x < a[mid])tail = mid - 1;else                                   {if (x > a[tail])tail = mid - 1;else head = mid + 1;}}mid = (head + tail)/2;}}





0 0