题目例如以下:
给定一个升序的整数数组。查找某一个值在数组中出现的索引號。比如。输入数组2,3。3,4,4。5;查找的数是3,则返回1,2。
时间复杂度要求为O(logN)。
初次拿到这个题目能够马上想到用二分查找来做。先比較中间的数和要查找的数。假设keyword(要查找的数)小于中间的数,那么在数组的左半部分继续查找。假设keyword大于中间的数。那么在数组的右半部分继续查找,假设keyword和中间的数相等。那么先比較中间数字的前一个数字是否和keyword相等,假设相等。继续用keyword和前一个数字的前一个数字比較,假设不等,那么当前数字就是要查找的数字。其所在的索引就是第一次出现的地方。对于结束的索引,能够用相似的方法来做,先比較中间数字的后一个数字是否和keyword相等,假设相等。继续用keyword和后一个数字的后一个数字比較,假设不等,那么当前数字就是要查找的数字,其所在的索引就是最后一次出现的地方。
可是这样做,最坏的情况下。时间复杂度会退化为O(N),即当数组是同一个数的时候。所以这样的方法不是时间上最优的。
事实上,本题目是二分查找的变种,我们能够分为两步来做,第一步,求得该数字第一次出现的索引,第二步,求得该数字最后一次出现的索引。
首先来看第一次出现的索引怎么来求。首先比較中间的数和要查找的数。假设keyword(要查找的数)小于中间的数,那么在数组的左半部分继续查找。假设keyword大于中间的数,那么在数组的右半部分继续查找,假设keyword和中间的数相等。那么比較中间数字的前一个数字是否和keyword相等。假设不相等,那么当前的中间索引就是第一次出现的索引。假设相等,那么继续在前半部分查找。详细的实现代码例如以下:
//寻找開始索引 int GetFirstTarget(int A[], int n, int target,int nStart,int nEnd) { if (nStart > nEnd) { return -1; } //中间索引 int nMid = nStart + ( (nEnd-nStart) >> 1); int nMidData = A[nMid]; while (nStart <= nEnd) { if (target > nMidData) { nStart = nMid+1; } else if (target < nMidData) { nEnd = nMid-1; } else if (target == nMidData) { if ((target != A[nMid-1] && nMid > 0) || nMid == 0) { return nMid; } else nEnd = nMid-1; } //更新中间值得索引和值 nMid = nStart + ( (nEnd-nStart) >> 1); nMidData = A[nMid]; } return -1; }
寻找最后一次出现的索引也能够用相似的思路来做:首先比較中间的数和要查找的数,假设keyword(要查找的数)小于中间的数,那么在数组的左半部分继续查找,假设keyword大于中间的数。那么在数组的右半部分继续查找。假设keyword和中间的数相等,那么比較中间数字的后一个数字是否和keyword相等。假设不相等,那么当前的中间索引就是最后一次出现的索引。假设相等,那么继续在后半部分查找。寻找第一个索引和最后一个索引的详细区别能够注意红色字体的字眼,详细的实现代码例如以下:
//寻找结束索引 int GetSecondTarget(int A[], int n, int target,int nStart,int nEnd) { if (nStart > nEnd) { return -1; } //中间索引 int nMid = nStart + ( (nEnd-nStart) >> 1); int nMidData = A[nMid]; while (nStart <= nEnd) { if (target > nMidData) { nStart = nMid+1; } else if (target < nMidData) { nEnd = nMid-1; } else if (target == nMidData) { if ((target != A[nMid+1] && nMid < n) || nMid == n-1) { return nMid; } else nStart = nMid+1; } //更新中间值得索引和值 nMid = nStart + ( (nEnd-nStart) >> 1); nMidData = A[nMid]; } return -1; }
最后就是主功能函数进行调用了,其代码例如以下:
vector<int> searchRange(int A[], int n, int target) { std::vector<int> vecIndex; vecIndex.resize(2); vecIndex[0] = -1; vecIndex[1] = -1; if (A == NULL || n <= 0) { return vecIndex; } vecIndex[0] = GetFirstTarget(A,n,target,0,n-1); vecIndex[1] = GetSecondTarget(A,n,target,0,n-1); return vecIndex; }
两次查找的时间复杂度都是O(logN),所以总的时间复杂度就是O(logN)。
最后。这个题目还有另外一个变种就是数字在排序数组中出现的次数