前言
之前面试准备秋招,重新翻起了《编程之美》。在第三章节看到了一道关于二分搜索的讨论,觉得有许多细节是自己之前也没怎么特别注意地方,比如二分搜索的初始条件,转化、终止条件之类的。
问题
找出一个有序(字典序)字符串数组 arr 中值等于字符串v的元素的序号,如果有多个元素满足这个条件,则返回其中序号最大的。
分析
如果去掉“返回序号最大的”,则标准的二分解法。但是数据中有重复元素,要求返回序号序号最大的元素序号。
以下是有BUG的解法:
int bisearch(int** arr, int b, int e, int* v)
{
int minIndex = b, maxIndex = e, midIndex;
while(minIndex < maxIndex)
{
midIndex = (minIndex + maxIndex) / 2;
if(strcmp(arr[midIndex], v) <=0)
midIndex = minIndex;
else
midIndex = maxIndex - 1;
}
if(!strcmp(arr[maxIndex], v))
return maxIndex;
else
return -1;
}
- 可能存在上溢出
midIndex = (minIndex + maxIndex) / 2;
咋一眼看去没什么大的问题,但是极端情况下可能导致错误。如果这是个32位的程序,32位有符号整数可以标识的范围-2^31 ~ 2^31,如果minIndex+maxIndex恰好超过了2^32,就会导致上溢出,此时midIndex变成负数。
想象一下,当minIndex=2, maxIndex=3, 而arr[minIndex] <= v时,midInde将始终等于minIndex,进入死循环。
正确解法
int bisearch(int** arr, int b, int e, int* v)
{
int minIndex = b, maxIndex = e, midIndex;
while(minIndex < maxIndex - 1)
{
midIndex = minIndex + (maxIndex - minIndex) / 2;
if(strcmp(arr[midIndex], v) <=0)
midIndex = minIndex;
else
midIndex = maxIndex;
}
if(!strcmp(arr[maxIndex], v))
return maxIndex;
else if(!strcmp(arr[maxIndex], v))
return minIndex;
else:
return -1;
}
扩展问题
给定一个有序(不降序)数组arr:
- 求任意一个使得arr[i]等于v,不存在则返回-1
- 求最小的i使得arr[i]等于v,不存在则返回-1
- 求最大的i使得arr[i]等于v,不存在则返回-1
- 求最大的i使得arr[i]小于v,不存在则返回-1
- 求最小的i使得arr[i]大于v,不存在则返回-1