二分查找

93 阅读3分钟

今天做一道题发现二分,但是关于循环的时候什么时候用 left < right ,什么时候用 <=

什么时候用 left = mid +1 还是left = mid 又搞不清了了

几天借着这些问题、在来梳理一下,其实这些选择都跟边界选择有关

704. 二分查找

左闭右闭 []

left,right := 0, len(arr) - 1 // 因为是左闭右闭区间 
for left <= right {
 // 为什么,因为左闭右闭,左边界和右边界可以相等
 if arr[mid] > right{ // 更新右边界
 
     right = mid -1  // 此时mid 这个值是无用的,不被包含在区间中,所以可以要用mid - 1
     
 }else if arr[mid] < right{ 更新左边界
 
     left = mid + 1 // 同理,mid这个值在需要的区间之外,大家可以画个图更形象

 }else{
     return mid
 }
}

左闭右开区间[)

for left < right {
 // 为什么,因为左闭右开,右边界不被包含在区间中,左边界和右边界不可以相等
 if arr[mid] > right{ // 更新右边界
 
     right = mid   // 此时mid 这个值是无用的,但是右边界是开区间,所以可以等于mid
     
 }else if arr[mid] < right{ // 更新左边界
 
     left = mid + 1 // 同理,mid这个值在需要的区间之外,大家可以画个图更形象
     
 }else{
     return mid
 }
}

有重复元素的二分,查找第一个元素位置和最后一个元素位置

思路

使用左闭右闭区间,那么left 可以等于 right

接下来就是关于mid的边界判定

if num[mid] > target r = mid - 1// 收缩右边界

if num[mid] < target l = mid + 1// 收缩左边界

if num[mid] = target r = mid - 1// 如果要找第一个重复元素这时候就要继续收缩右边界,知道区间所有元素都小于target,那时候最后一步判断l会向右移位一步找到target

Code

func searchRange(nums []int, target int) []int {
	return []int{findLeftNum(nums, target), findRightNum(nums, target)}
}
func findRightNum(nums []int, target int) int {
	l, r := 0, len(nums)-1
	for l <= r {
		mid := l + (r-l)/2
		if nums[mid] < target {
			l = mid + 1
		} else if nums[mid] > target {
			r = mid - 1
		} else {
			l = mid + 1
		}
	}
    // 边界判定,这时候要输出r,而r一直在减小
	if r < 0 || nums[r] != target{
		return -1
	}
	return r
}
func findLeftNum(nums []int, target int) int {
	l, r := 0, len(nums)-1
	for l <= r {
		mid := l + (r-l)/2
		if nums[mid] < target {
			l = mid + 1
		} else if nums[mid] > target {
			r = mid - 1
		} else {
			r = mid - 1
		}
	}
	if l >= len(nums) || nums[l] != target{
		return -1
	}
	return l
}

33. 搜索旋转排序数组

[TOC]

思路1

使用二分,把一个完成的区间分成两块,一块有序,一块无序

有序无序可以通过 num[0] 和 nums[mid]的关系判断, nuns[0] <= nums[mid],说 明 0,mid 是有序的,可以通过二分判断这个区间

反之,就判断 mid,r 区间的数据

Code

func search(nums []int, target int) int {
	// 根据第一个节点和mid的关系判断是否有序
	l, r := 0, len(nums)-1
	for l <= r {
		mid := l + (r-l)>>1
		if nums[mid] == target {
			return mid
		}
		if nums[0] <= nums[mid] {
			if nums[mid] > target && nums[l] <= target {
				r = mid - 1
			} else {
				l = mid + 1
			}
		} else {
			if nums[mid] < target && nums[r] >= target {
				l = mid + 1
			} else {
				r = mid - 1
			}
		}
	}
	return -1
}
  • 思路二:就是把数组变成有序

这种方法其实有些取巧,如果有数字x< nums[0],就把这个数字 +10000 (观察题发现数字最大是10000),这样就能直接用二分来搜索了

func search(nums []int, target int) int {
   // 根据第一个节点和mid的关系判断是否有序
   target += 10000
   l, r := 0, len(nums)-1
   for l <= r {
      mid := l + (r-l)>>1
      if nums[mid] < nums[0] {
         nums[mid] += 10000
      }
      if nums[mid] == target {
         return mid
      }
      if nums[mid] > target {
         r = mid - 1
      } else {
         l = mid + 1
      }
   }
   return -1
}