题目:
对于一个有序数组arr,再给定一个整数num,请在arr中找到num这个数出现的最左边的位置。
给定一个数组arr及它的大小n,同时给定num。请返回所求位置。若该元素在数组中未出现,请返回-1。
测试用例:
[1,2,3,3,4],5,3返回:2
思路:
二分查找的变种。标准的二分查找有3个分支:arr[mid] == num; arr[mid] < num; arr[i] > num,最终目的是找到一个数。就这题来说,我们希望找到找到有序数组中num出现的第一个位置,那么当arr[mid] >= num 时, mid和mid右边的元素都大于或等于num,应该从mid左边的元素中取寻找;如果arr[mid] < num,那么所有小于等于mid的元素一定不是num,应该从mid+1开始的右边元素中寻找。所以这题需要两个分支。
二分查找最大的难点还是在于代码实现。在二分查找中,最重要的就是维护查找区间的开闭性一致,以及什么时候退出循环。
假设在进入循环前,取left = 0, right = len(arr)-1,说明我们的查找区间是一个闭区间[0 : len(arr)-1]。那么应该保持循环条件left <= right。在循环的过程中,如果arr[left] < arr[mid],让left = mid+1,如果arr[right] >= arr[mid], right = mid-1,查找区间的变化等价于[mid+1 : right] 或 [left : mid-1],当left > right时,查找区间长度为0,此时跳出循环。无论是因为 left == right 时 arr[left] < num导致了 left+1 还是 arr[right] >= num 导致了 right-1,都可以对arr[righ+1] 或是 arr[left]进行判断。当然,还需要判断特殊情况:即num大于有序数组中的所有数,left = len(arr)。
假设在进入循环前,取left = 0, right = len(arr),说明我们的查找区间是一个闭区间[0 : len(arr)-1)。那么应该保持循环条件left < right。在循环的过程中,如果arr[left] < arr[mid],让left = mid+1,如果arr[right] >= arr[mid], right = mid,查找区间的变化等价于[mid+1 : right) 或 [left : mid),当left == right时,查找区间长度为0,此时跳出循环。因为 arr[mid] >= num 时,右边界维持不动,所以循环结束时right及right右边的数是大于等于num的,而left左边的数是小于num的,且left == right,所以应该判断arr[left] 或 arr[right]。 同样需要特殊判断
代码:
# -*- coding:utf-8 -*-
class LeftMostAppearance:
def findPos(self, arr, n, num):
# 保持闭区间
left, right = 0, n-1
while left <= right:
mid = left + (right-left)//2
if arr[mid] < num:
left = mid+1
else:
right = mid-1
if left >= n:
return -1
if arr[left] == num:
return left
else:
return -1
class LeftMostAppearance:
def findPos(self, arr, n, num):
# 保持左闭右开区间
left, right = 0, n
while left < right:
mid = left + (right-left)//2
if arr[mid] < num:
left = mid+1
else:
right = mid
if left >= n:
return -1
if arr[left] == num:
return left
else:
return -1