二分查找总结题型 - CSNotes

二分查找有很多细节要注意。

  1. x的平方根

对于 x = 8,它的开方是 2.82842…,最后应该返回 2 而不是 3。在循环条件为 l <= h 并且循环退出时,h 总是比 l 小 1,也就是说 h = 2,l = 3,因此最后的返回值应该为 h 而不是 l。
解法一:

class Solution:
    def mySqrt(self, x: int) -> int:
        # 直接返回的情况
        if x <= 1:
            return x
        # 不是index了,而是找1~x中根号下x的整数值。所以low、high初始化为1,x
        l, h = 1, x
        while l <= h:
            # 整数
            mid = (l + h) >> 1
            # sqrt取整数部分
            sqrt = x // mid
            if sqrt == mid:
                return mid
            elif mid > sqrt:
                h = mid - 1
            else:
                l = mid + 1
        return h

解法二:

class Solution:
    def mySqrt(self, x: int) -> int:
        l, r, ans = 0, x, -1
        while l <= r:
            mid = (l + r) // 2
            if mid * mid <= x:
                ans = mid
                l = mid + 1
            else:
                r = mid - 1
        return ans


  1. 寻找比目标字母大的最小字母

给你一个排序后的字符列表 letters ,列表中只包含小写英文字母。另给出一个目标字母 target,请你寻找在这一有序列表里比目标字母大的最小字母。

在比较时,字母是依序循环出现的。举个例子:

如果目标字母 target = ‘z’ 并且字符列表为 letters = [‘a’, ‘b’],则答案返回 ‘a’

寻找有序列表里比目标字母大的最小字母,相当于寻找右边界,左指针指向的元素。若target存在在letters中,那么返回左指针指向的元素;若target不存在在letters中 (那么left指针会停在index = length, right指针会停在left - 1即index = length - 1), 要么是比letters数组中的字母都小,要么是都大,这两种情况都可以返回letters[0]。

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

  1. 有序数组中的单一元素

https://leetcode.com/problems/single-element-in-a-sorted-array/discuss/100732/Short-compare-numsi-with-numsi1

Simply find the first index whose “partner index” (the index xor 1) holds a different value. Every 2 numbers are partner. (even,odd), (even,odd). If mid is even, it’s partner is next odd, if mid is odd, it’s partner is previous even. odd xor 1 = odd-1 even xor 1 = even+1

自己调试一遍,更清晰!

def singleNonDuplicate(self, nums):
    lo, hi = 0, len(nums) - 1
    while lo < hi:
        mid = (lo + hi) / 2
        if nums[mid] == nums[mid ^ 1]:
            lo = mid + 1
        else:
            hi = mid
    return nums[lo]

若数组无序,那么只用异或即可:
原理:
在这里插入图片描述
二分查找总结题型 - CSNotes_第1张图片

class Solution:
    def singleNumber(self, nums: List[int]) -> int:
        return reduce(lambda x, y: x ^ y, nums)


reduce函数的用法:
reduce(function, iterable[, initializer])
二分查找总结题型 - CSNotes_第2张图片
lambda函数的用法:
函数名 = lambda 输入:返回值(输出),一般为输入的表达式

  1. 第一个错误的版本

相当于找到一个边界,这里不分上边界or下边界,所以也没有nums[mid] >= target (找下边界)还是nums[mid] > target (找上边界),只有isBadVersion(mid) is True or False。最后找到的边界,left是第一个错误的版本,right是最后一个正确的版本

注意初始化,这里初始化成1-n是因为index(版本号)就是1-n。

class Solution:
    def firstBadVersion(self, n):
        """
        :type n: int
        :rtype: int
        """
        # 这里初始化成1~n是因为index(版本号)就是1~n
        l, r = 1, n
        #这里应该是 l <= r 才能使l, r逼近同一个值
        while l <= r:
            mid = (l + r) // 2
            if isBadVersion(mid):
                #如果说该mid是错误版本,那么应该向前查找
                r = mid - 1
            else:
                #如果说该mid是正确版本,那么应该向后查找
                l = mid + 1
        #最后会逼近到第一个错误版本号l
        return l 


  1. 寻找旋转排序数组的最小值

1)找分界线,然后看看是返回分界线左边还是分界线右边的元素
https://leetcode.com/problems/find-minimum-in-rotated-sorted-array/solution/

class Solution(object):
    def findMin(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        # if 数组只有一个数
        if len(nums) == 1:
            return nums[0] 
        left, right = 0, len(nums) - 1
        # if 数组有序
        if nums[right] > nums[0]:
            return nums[0]
        # 二分查找
        while left <= right:
            mid = left + (right - left) // 2
            
            # 看看分界线mid左边和右边的元素,如果符合“前高后低”,那么低的那个就是要找到min
            if nums[mid] > nums[mid + 1]:
                return nums[mid + 1]
            if nums[mid - 1] > nums[mid]:
                return nums[mid]
                
            # 二分查找更新left和right
            if nums[mid] > nums[0]:
                left = mid + 1
            else:
                right = mid - 1
  1. converge the left and right bounds, never disqualify the index for a possible minimum value.
    https://leetcode.com/problems/find-minimum-in-rotated-sorted-array/discuss/158940/Beat-100%3A-Very-Simple-(Python)-Very-Detailed-Explanation
class Solution:
    def findMin(self, nums: List[int]) -> int:
        left, right = 0, len(nums)-1
        while left < right: # 最后停在left == right
            mid = (left + right) // 2
            if nums[mid] > nums[right]: # mid处元素 > right处元素,说明[mid+1, len(nums)-1]有最小值,left = mid + 1
                left = mid + 1
            else: # mid处元素 <= right处元素,说明[mid, len(nums)-1]是递减的,要么mid是最小值,要么最小值在mid前面
                right = mid
        # 直到 left == right,找到最小值!
        return nums[right]
  1. 在排序数组中查找元素的第一个和最后一个位置(找上下边界)
    1)解法一:我们将寻找 target 最后一个位置,转换成寻找 target+1 第一个位置,再往前移动一个位置。这样我们只需要实现一个二分查找代码即可。
public int[] searchRange(int[] nums, int target) {
    int first = findFirst(nums, target);
    int last = findFirst(nums, target + 1) - 1;
    if (first == nums.length || nums[first] != target) {
        return new int[]{-1, -1};
    } else {
        return new int[]{first, Math.max(first, last)};
    }
}

private int findFirst(int[] nums, int target) {
    int l = 0, h = nums.length; // 注意 h 的初始值
    while (l < h) {
        int m = l + (h - l) / 2;
        if (nums[m] >= target) {
            h = m;
        } else {
            l = m + 1;
        }
    }
    return l;
}

2)解法二:用两个二分查找分别找出第一个位置(下边界,返回left)和最后一个位置(上边界,返回right)

class Solution {
public:
    < @note 寻找target的左边界
    int leftBound(vector<int> &nums, int target) {
        int left = 0, right = nums.size()-1;
        while (left <= right) {
            int mid = left + (right-left)/2;
            if (nums[mid] >= target) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        /*
         * 当target小于nums中所有值时,left=0, right=-1
         * 当target大于nums中所有值时,left=nums.size(), right=nums.size()-1
         * 当target处于nums的最大值和最小值之间,但没有相等值,就需要第二个判断*/
        if (left >= nums.size() || nums[left] != target) {
            return -1;
        }
        return left;
    }

    < @note 寻找target的右边界
    int rightBound(vector<int> &nums, int target) {
        int left = 0, right = nums.size()-1;
        while (left <= right) {
            int mid = left + (right-left)/2;
            if (nums[mid] > target) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        if (right < 0 || nums[right] != target) {
            return -1;
        }
        return right;
    }

    vector<int> searchRange(vector<int>& nums, int target) {
        int left = leftBound(nums, target);
        int right = rightBound(nums, target);
        return {left, right};
    }
};

lc某题. 查找==target的第一个或最后一个元素的index,若找不到则返回-1

注意在最后返回时不是返回left(下边界,返回left,得到第一个=target的元素index),或right(上边界,返回right,得到最后一个=target的元素index),而是要写成:

return left if left < len(nums) else -1
or
return right if right >= 0 else -1

你可能感兴趣的:(刷题,python)