二分查找经典问题整理

模板

1、二分查找(数组单调递增)

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        left, right = 0, len(nums) - 1
        while left <= right:
            mid = (left + right) // 2
            if nums[mid] == target:
                return mid
            elif nums[mid] < target:
                left = mid + 1
            elif nums[mid] > target:
                right = mid - 1
        return -1

2、bisect_right

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        left, right = 0, len(nums)
        while left < right:
            mid = (left + right) // 2
            if nums[mid] <= target:
                left = mid + 1
            else:
                right = mid
        return left

3、bisect_left 

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        left, right = 0, len(nums)
        while left < right:
            mid = (left + right) // 2
            if nums[mid] < target:
                left = mid + 1
            else:
                right = mid
        return left

 注: 1、由于整除一般向下取整,所以left = mid + 1防止死循环

        2、根据1的性质,当查找的数介于两数之间时,都取较大者

        3、当查找的数在数组中存在多个时,bisect_left取第一个数的位置,bisect_right取最后一个数的下一个位置。

       4、待查找的数不小于数组的最大值时,bisect_right返回len(nums),待查找的数大于数组最大值时,bisect_left返回len(nums)


例题:

class Solution:
    def searchRange(self, nums: List[int], target: int) -> List[int]:
        left, right = bisect_left(nums, target), bisect_right(nums, target)
        if left == right: return -1, -1
        return left, right - 1
class Solution:
    def searchInsert(self, nums: List[int], target: int) -> int:
        return bisect_left(nums, target)
class Solution:
    def longestObstacleCourseAtEachPosition(self, obstacles: List[int]) -> List[int]:
        res, arr = [], []
        for o in obstacles:
            idx = bisect_right(arr, o)
            res.append(idx + 1)
            if idx >= len(arr):
                arr.append(o)
            else:
                arr[idx] = o
        return res

特征查找

class Solution:
    def findPeakElement(self, nums: List[int]) -> int:
        nums = [-float('inf')] + nums + [-float('inf')]
        left, right = 0, len(nums) - 1
        while left <= right:
            mid = (left + right) // 2
            if nums[mid - 1] < nums[mid] > nums[mid + 1]:
                return mid - 1
            elif nums[mid] < nums[mid + 1]:
                left = mid + 1
            elif nums[mid] < nums[mid - 1]:
                right = mid - 1
        return -1
class Solution:
    def singleNonDuplicate(self, nums: List[int]) -> int:
        lo, hi = 0, len(nums) - 1
        while lo < hi:
            mid = lo + (hi - lo) // 2
            if mid % 2 == 1:
                mid -= 1
            if nums[mid] == nums[mid + 1]:
                lo = mid + 2
            else:
                hi = mid
        return nums[lo]
class Solution:
    def findMin(self, nums: List[int]) -> int:
        left, right = 0, len(nums) - 1
        while left < right:
            mid = (left + right) // 2
            if 0 < mid < len(nums) - 1:        
                if nums[mid - 1] > nums[mid] <= nums[mid + 1]:
                    return nums[mid]
            if nums[mid] < nums[right]:
                right = mid - 1
            elif nums[mid] > nums[right]:
                left = mid + 1
            else:
                right -= 1
        return nums[left]
class Solution:
    def missingElement(self, nums: List[int], k: int) -> int:
        n = len(nums)
        l, r = 0, n
        while l < r:
            mid = (l + r) // 2
            if nums[mid] - (nums[0] + mid) >= k:
                r = mid
            else:
                l = mid + 1
        return nums[l - 1] + k - (nums[l - 1] - (nums[0] + (l - 1)))

还可以写成如下形式:

class Solution:
    def missingElement(self, nums: List[int], k: int) -> int:
        self.__class__.__getitem__ = lambda self, mid: nums[mid] - (nums[0] + mid) >= k
        l = bisect.bisect_left(self, True, 0, len(nums))
        return nums[l - 1] + k - (nums[l - 1] - (nums[0] + (l - 1)))
class Solution:
    def findPeakGrid(self, mat: List[List[int]]) -> List[int]:
        m, n = len(mat), len(mat[0])
        l, r = 0, m - 1
        @cache
        def getMax(i):
            maxl, maxi = 0, 0
            for j in range(n):
                if mat[i][j] > maxl:
                    maxl, maxi = mat[i][j], j
            return maxl, maxi

        def getE(i, j):
            if not 0 <= i < m and 0 <= j < n: return -1
            return mat[i][j]

        while l <= r:
            mid = (l + r) // 2
            mx, idx = getMax(mid)
            if mx > getE(mid - 1, idx) and mx > getE(mid + 1, idx):
                return [mid, idx]
            elif getE(mid - 1, idx) > getE(mid + 1, idx):
                r = mid - 1
            else:
                l = mid + 1   
        
        return -1

class Solution:
    def superEggDrop(self, k: int, n: int) -> int:
        @cache
        def dfs(t, k):
            if t == 0 or t == 1 or k == 1: return t
            left, right = 1, t
            while left + 1 < right:
                mid = (left + right) // 2
                t1 = dfs(mid - 1, k - 1) + 1
                t2 = dfs(t - mid, k) + 1
                if t1 < t2:
                    left = mid
                elif t1 > t2:
                    right = mid
                else:
                    return t1
            return 1 + min(max(dfs(x - 1, k - 1), dfs(t - x, k)) for x in (left, right))
        
        return dfs(n, k)

矩阵查找

搜索二维矩阵 IIicon-default.png?t=N7T8https://leetcode.cn/problems/search-a-2d-matrix-ii/

 

class Solution:
    def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
        if not matrix or len(matrix) == 0:
            return False
        m, n = 0, len(matrix[0]) - 1
        while m < len(matrix) and n >= 0:
            if matrix[m][n] == target:
                return True
            elif matrix[m][n] > target:
                n -= 1
            else:
                m += 1
        return False

二叉搜索树

二叉搜索树中的搜索icon-default.png?t=N7T8https://leetcode.cn/problems/search-in-a-binary-search-tree/

class Solution:
    def searchBST(self, root: TreeNode, val: int) -> TreeNode:
        cur = root
        while cur:
            if cur.val == val:
                return cur
            elif cur.val < val:
                cur = cur.right
            else:
                cur = cur.left
        return None

二叉搜索树中的中序后继icon-default.png?t=N7T8https://leetcode.cn/problems/P5rCT8/

class Solution:
    def inorderSuccessor(self, root: 'TreeNode', p: 'TreeNode') -> 'TreeNode':
        cur, res = root, None
        while cur:
            if cur.val > p.val:
                res = cur
                cur = cur.left
            else:
                cur = cur.right        
        return res

完全二叉树的节点个数icon-default.png?t=N7T8https://leetcode.cn/problems/count-complete-tree-nodes/

三分查找

找到最大整数的索引icon-default.png?t=N7T8https://leetcode.cn/problems/find-the-index-of-the-large-integer/      均分成三分,不能均分的,保证前两份相等,然后将两个相等数量份的进行比较,如果相等,则在第三份上,反之在前两份上

class Solution:
    def getIndex(self, reader: 'ArrayReader') -> int:
        l, r = 0, reader.length() - 1
        while l < r:
            n = (r - l + 1) // 3 + ((r - l + 1) % 3 > 0)
            res = reader.compareSub(l, l + n - 1, l + n, l + 2 * n - 1)
            if res == 1:
                r = l + n - 1
            elif res == -1:
                l, r = l + n, l + 2 * n - 1
            else:
                l = l + 2 * n
        return l

二分查找猜结果

用途:直接求解比较困难的问题

前提:1、有上下边界

           2、能根据结果缩小边界(函数具有单调性)


class Solution:
    def minEatingSpeed(self, piles: List[int], h: int) -> int:
        left, right = ceil(sum(piles) / h), max(piles)
        while left < right:
            mid = left + (right - left) // 2
            H = 0
            for p in piles:
                H += ceil(p / mid)
            if H > h:
                left = mid + 1
            else:
                right = mid
        return left
class Solution:
    def longestRepeatingSubstring(self, s: str) -> int:
        @lru_cache(None)
        def check(k):
            pre = set()
            for i in range(n - k + 1):
                cur = s[i: i + k]
                if cur in pre:
                    return True
                pre.add(cur)
            return False

        n = len(s)
        low, high = 1, n
        while low < high:
            mid = low + (high - low) // 2
            if check(mid):
                low = mid + 1
            else:
                high = mid
        
        return high - 1

类似题:最长重复子数组 

class Solution:
    def matrixMedian(self, grid: List[List[int]]) -> int:
        m, n = len(grid), len(grid[0])
        def check(x):
            cnt = 0
            for row in grid:
                cnt += bisect_right(row, x)
            return cnt <= n * m // 2
        
        l, r = min(row[0] for row in grid), max(row[-1] for row in grid)
        while l < r:
            mid = (l + r) // 2
            if check(mid):
                l = mid + 1
            else:
                r = mid
        return l

类似题:有序矩阵中第 K 小的元素 

class Solution:
    def kthSmallestSubarraySum(self, nums: List[int], k: int) -> int:
        def getK(x):
            res = 0
            j, s = 0, 0
            for i in range(len(nums)):
                s += nums[i]
                while s > x:
                    s -= nums[j]
                    j += 1
                res += i - j + 1
            return res
            
        l, r = min(nums), sum(nums)
        while l < r:
            mid = (l + r) // 2
            if getK(mid) < k:
                l = mid + 1
            else:
                r = mid
        return l

        对于每次二分的值 x,水量对于该值的木桶需要往外倒水,水量小于该值的木桶应该被倒水,往外倒水的水量之和记为 a,需要水量之和记为 b (注意不能忘记损失比例),只要满足 a⩾b 则 x 可以实现,因为多余的水量可以互相倒水,每次倒水存在损失比例,故而可以逐渐逼近 x。

class Solution:
    def equalizeWater(self, buckets: List[int], loss: int) -> float:
        n, l, r, s = len(buckets), 0, max(buckets), sum(buckets)

        def helper(x):
            s1 = s2 = 0
            for b in buckets:
                if b > x:
                    # 倒掉了多少水
                    s1 += b - x
                else:
                    # 至少需要多少水
                    s2 += (x - b) * 100 / (100 - loss)
            return s1 >= s2

        while (r - l) > 1e-5:
            mid = (l + r) / 2
            if helper(mid):
                l = mid
            else:
                r = mid - 0.000001
        return l
class Solution:
    def minimizeSet(self, d1: int, d2: int, uniqueCnt1: int, uniqueCnt2: int) -> int:
        lcm = math.lcm(d1, d2)
        def check(m: int) -> bool:
            return m - m // d1 >= uniqueCnt1 \
                   and  m - m // d2 >= uniqueCnt2 \
                   and m - m // lcm >= uniqueCnt1 + uniqueCnt2
        return bisect_left(range((uniqueCnt1 + uniqueCnt2) * 2 - 1), True, key=check)

注意,只有被二分的函数单调时,才可以使用,否则,例如:根据限制分割消息,则导致结果错误。

有序数组二分查找

class Solution:
    def closestRoom(self, rooms: List[List[int]], queries: List[List[int]]) -> List[int]:
        from sortedcontainers import SortedList
        rooms.sort(key=lambda x:-x[1])
        res = [0] * len(queries)
        arr, j = SortedList(), 0
        for i, (p, s) in sorted([(i, q) for i, q in enumerate(queries)], key=lambda x:-x[1][1]):
            while j < len(rooms) and rooms[j][1] >= s:
                arr.add(rooms[j][0])
                j += 1          
            idx = arr.bisect_right(p)
            r, d = -1, inf
            if idx > 0:
                if abs(arr[idx - 1] - p) < d:
                    r, d = arr[idx - 1], abs(arr[idx - 1] - p)
            if idx < len(arr):
                if abs(arr[idx] - p) < d:
                    r, d = arr[idx], abs(arr[idx] - p) 
            res[i] = r
        return res

折半查找

class Solution:
    def splitArraySameAverage(self, nums: List[int]) -> bool:
        n = len(nums)
        m = defaultdict(set)
        def dfs1(i, c, s):
            if i == n // 2:
                m[c].add(s)
                return
            dfs1(i + 1, c + 1, s + nums[i])
            dfs1(i + 1, c, s)
        
        ss = sum(nums) 
        dfs1(0, 0, 0)
        def dfs2(i, c, s):
            if i == n:
                for c2 in m:
                    if not 0 < (c + c2) < n: continue
                    a1 = (c + c2) * ss / n - s
                    a2 = (c + c2) * ss // n - s
                    if abs(a1 - a2) < 1e-6 and a2 in m[c2]:
                        return True
                else:
                    return False
            if dfs2(i + 1, c + 1, s + nums[i]): return True
            if dfs2(i + 1, c, s): return True
            return False
        
        return dfs2(n // 2, 0, 0)
class Solution:
    def minimumDifference(self, nums: List[int]) -> int:
        n = len(nums)
        from sortedcontainers import SortedList
        m = defaultdict(SortedList)

        def dfs1(i, c, s):
            if i == n // 2:
                m[c].add(s)
                return
            dfs1(i + 1, c + 1, s + nums[i])
            dfs1(i + 1, c, s)
        
        ss = sum(nums) 
        dfs1(0, 0, 0)
        minl = inf
        def dfs2(i, c, s):
            if i == n:
                c2 = n // 2 - c
                nonlocal minl
                idx = m[c2].bisect_right((ss - 2* s) / 2)
                if idx < len(m[c2]): 
                    s1 = m[c2][idx]
                    left = s1 + s
                    right = ss - s1 - s
                    minl = min(minl, abs(left - right))
                if idx - 1 >= 0:
                    s1 = m[c2][idx - 1]
                    left = s1 + s
                    right = ss - s1 - s
                    minl = min(minl, abs(left - right))
                return
            dfs2(i + 1, c + 1, s + nums[i])
            dfs2(i + 1, c, s)
        
        dfs2(n // 2, 0, 0)
        return minl

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值