[Notes] Binary Search

Posted by 西维蜀黍的 OJ Blog on 2023-09-10, Last Modified on 2025-03-25
  • [l, r] 表示当前还未扫描的范围

    • 因而,二分法的继续执行条件用: l <= r

      • 因为如果只有一个元素,那么 l == r,如果不包含等号,那么就会跳过这个元素
      • 当 不满足时,表示当前等待扫描范围为空,则停止扫描

解析

* 33. Search in Rotated Sorted Array

  • 找到 [l, m] or [m, r] 哪边是 sorted 部分,用小于该 sorted 部分的最小值 或 大于该 sorted 部分的最大值来判断

    • 用 nums [m] < nums [r] 来保证 the right portion is sorted

    • 反之,说明 the left portion is sorted

# burte force
#  - time: O(n)

# binary search - 写法1 
#  - time: O(logn)
#  - space: O(1)
	def search(self, nums: List[int], target: int) -> int:
		l, r = 0, len(nums) - 1
		while l <= r:
			m = (l + r) // 2
			if nums[m] == target:
				return m

			# the right portion is sorted
			if nums[m] < nums[r]: # e.g., [10, 11, 7, 8, 9]
				if target > nums[r] or target < nums[m]:
					r = m - 1
				else:
					l = m + 1
			else: # the left portion is sorted, e.g., [11, 12, 13, 14, 10]
				if target > nums[m] or target < nums[l]:
					l = m + 1
				else:
					r = m - 1
		return -1
  
# binary search - 写法2  
#  - time: O(logn)
#  - space: O(1)
    def search(self, nums: List[int], target: int) -> int:
        l, r = 0, len(nums) - 1
        while l <= r:
            m = (l + r) // 2
            if nums[m] == target:
                return m

            if nums[l] <= nums[m]:
                # the left portion is sorted, e.g., [11, 12, 13, 14, 10]. 
                # Must use <=, because we have the case where m == l, e.g., [2,1]
                if target < nums[l] or target > nums[m]:
                    l = m + 1
                else:
                    r = m
            else:  # the right portion is sorted
                if target < nums[m] or target > nums[r]:
                    r = m - 1
                else:
                    l = m

        return -1

ref

todo - 34. Find First and Last Position of Element in Sorted Array

# binary search - 写法 1
#  - time: O(logn)
#  - space: O(1)  
	def searchRange(self, nums: List[int], target: int) -> List[int]:
		l, r = 0, len(nums) - 1
		while l <= r:
			m = (l + r) // 2
			if target < nums[m]:
				r = m - 1
			elif nums[m] < target:
				l = m + 1
			else:  # entering here means we found at least 1 target
				start, end = m, m
				pre_l, pre_r = l, r

				# to get min start
				l, r = pre_l, m
				while l <= r:  # l and r are the current searching ranges (inclusive)
					m = (l + r) // 2
					if nums[m] == target:
						r = m - 1
						start = min(start, m)
					elif nums[m] < target:
						l = m + 1

				# to get max end
				l, r = m, pre_r
				while l <= r:
					m = (l + r) // 2
					if nums[m] == target:
						l = m + 1
						end = max(end, m)
					elif nums[m] > target:
						r = m - 1

				return [start, end]  # if found at least 1 target, always return from here

		return [-1, -1]  # If not found the target, always return from here
  
# binary search - 写法2 - more elegent
#  - time: O(logn)
#  - space: O(1)  
	def searchRange(self, nums: List[int], target: int) -> List[int]:
		# if leftMost is true, find the leftmost index, otherwise find the rightmost index
		def binarySearch(leftMost):
			left, right = 0, len(nums) - 1,
			i = -1
			while left <= right:
				m = (left + right) // 2
				if target < nums[m]:
					right = m - 1
				elif nums[m] < target:
					left = m + 1
				else:
					i = m
					if leftMost:
						right = m - 1
					else:
						left = m + 1
			return i

		# time: O(2logn)
		l = binarySearch(True)
		r = binarySearch(False)
		return [l, r]

ref

74. Search a 2D Matrix

# binary search 
#  - time: O(log(m+n) (which reduces to O(log(m*n)))
#  - space: O(1)
def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
		top, bottom = 0, len(matrix) - 1
		foundRow = False
		while top <= bottom:
			row = (top + bottom) // 2
			if matrix[row][0] <= target and matrix[row][-1] >= target:
				foundRow = True
				break
			elif matrix[row][0] > target:
				bottom = row - 1
			else:
				top = row + 1

		if not foundRow:
			return False

		row = (top + bottom) // 2
		left, right = 0, len(matrix[0]) - 1
		while left <= right:
			col = (left + right) // 2
			if matrix[row][col] == target:
				return True
			elif matrix[row][col] > target:
				right = col - 1
			else:
				left = col + 1
		return False

ref

*153. Find Minimum in Rotated Sorted Array

# binary search - more elegent
#  - time: O(logn)
#  - space: O(1)
	def findMin(self, nums: List[int]) -> int:
		cur_min = nums[0]  # record the min that we have seen
		l, r = 0, len(nums) - 1
		while l <= r:  # [l: r+1] are the current valid searching range, so we will search until empty range with recording the min that we have seen
			if nums[l] < nums[r]:  # meaning the array is sorted
				return min(cur_min, nums[l])

			m = (l + r) // 2
			if nums[l] <= nums[m]:  # meaning the left part [l: m+1] is the sorted portion. Must use <=, since l may be equal to m, e.g., [2, 1]
				cur_min = min(cur_min, nums[l])
				l = m + 1  # search the right part then
			else:  # nums[m] < nums[l] and nums[l] > nums[r]
				cur_min = min(cur_min, nums[m])
				r = m - 1  # search the left part then

		return cur_min
  • l <= r r = m-1l = m + 1,存下一个最小值,然后不断去取最小值,[l, r] 表示当前的查找范围(如果已经把一个值考虑进最小值 int 后,则后续的查找范围就不需要再包含该值了)

ref

todo - 278. First Bad Version

	def firstBadVersion(self, n: int) -> int:
		l, r = 1, n
		res = n
		while l <= r:
			m = (l + r) // 2
			if isBadVersion(m):
				res = min(res, m)
				r = m - 1
			else:
				l = m + 1
		return res
# Recursive Binary Search
# - time: O(logn)
# - space: O(1)
   def binary_search(self, l: int, r: int, nums: List[int], target: int) -> int:
        if l > r:
            return -1
        m = l + (r - l) // 2
        
        if nums[m] == target:
            return m
        if nums[m] < target:
            return self.binary_search(m + 1, r, nums, target)
        return self.binary_search(l, m - 1, nums, target)

    def search(self, nums: List[int], target: int) -> int:
        return self.binary_search(0, len(nums) - 1, nums, target)
      
# Iterative Binary Search
# - time: O(logn)
# - space: O(1)
 def search(self, nums: List[int], target: int) -> int:
		l, r = 0, len(nums) - 1
		while l <= r: # 必须是 <=,因为如果是 <,nums: [5], target: 5 的情况就cover不了
			m = (l + r) // 2 # (l + r) // 2 may have the over-flow issue in other languages (Python is fine). l + (r - l) // 2 is perfect in all languages
			if target == nums[m]:
				return m
			elif target > nums[m]:
				l = m + 1
			else:
				r = m - 1
		return -1

ref

875. Koko Eating Bananas

  • 用一个 min_speed 去记下当前的最小速度,然后用二分法去找到最小速度(每次都是 l = mid +1 or r = m -1 以标识当前的查找范围)
  • 如果 l > r 时,说明以搜索完当前查找范围
# brute force 
#  - time: O(max(piles)*piles)
#  - space: O(1)
    def minEatingSpeed(self, piles: List[int], h: int) -> int:
        speed = 1
        while True:
            totalTime = 0
            for pile in piles:
                totalTime += math.ceil(pile / speed)
            
            if totalTime <= h:
                return speed
            speed += 1
        return speed

# binary search 
#  - time: O(log(max(piles))*piles)
#  - space: O(1)
    def minEatingSpeed(self, piles: List[int], h: int) -> int:
        l, r = 1, max(piles)
        res = r

        while l <= r:
            k = (l + r) // 2

            totalTime = 0
            for p in piles:
                totalTime += math.ceil(float(p) / k)
            if totalTime <= h:
                res = k
                r = k - 1
            else:
                l = k + 1
        return res      

ref

todo - 981. Time Based Key-Value Store

class TimeMap:
	def __init__(self):
		self.m = {}  # space: O(1)

	def set(self, key: str, value: str, timestamp: int) -> None:
		if key not in self.m:
			self.m[key] = []
		self.m[key].append([timestamp, value])

	def get(self, key: str, timestamp: int) -> str:
		if key not in self.m:
			return ""

		values = self.m[key]
		res = ""
		# Since "All the timestamps timestamp of set are strictly increasing", the timestamps of self.m[<key>] is increasing.
		# Hence, we could use binary search
		l, r = 0, len(values) - 1
		while l <= r:
			m = (l + r) // 2
			if timestamp == values[m][0]:
				return values[m][1]
			elif timestamp < values[m][0]:
				# the timestamp of the current m position is still > the input values[m][0],
				# and hence cannot consider the m position and have to consider the left part,
				# like get("foo",1), [[10, "test1"], [11, "test2"] ...], we return "", since only consider <=1
				r = m - 1
			else:
				# m position has been considered into res, and continue to search the right part
				l = m + 1
				res = values[m][1]
		return res

ref