Grind75第8天 | 278.第一个错误的版本、33.搜索旋转排序数组、981.基于时间的键值存储

278.第一个错误的版本

题目链接:https://leetcode.com/problems/first-bad-version

解法:

二分查找。

如果一个版本为错误版本(isBadVersion为True),那么第一个错误版本在该版本左侧(包括该版本);如果一个版本为正确版本,那么第一个错误版本在该版本的右侧(不包括该版本)。

如此不断收缩范围,直到left和right指针相等。

注意,这里的判断条件是 while left < right,为啥不是 while left <= right呢?我之前二分查找统一写成左闭右闭,所以判断条件是 while left <= right ,但是这个题,right 的更新是 right = mid,而不是 right = mid + 1,那么如果判断条件加了 = 号,就可能会无限循环下去。比如 [1,2,3,4,5],版本为 [false, false, false, true, true],那么第一个错误版本是4,第一轮判断后 left=4,right=5,第二轮判断时 mid=4,那么right=mid=4,这样 while left <= right 就会一直满足,永远循环下去。

边界条件:无

时间复杂度:O(logn)

空间复杂度:O(1)

# The isBadVersion API is already defined for you.
# def isBadVersion(version: int) -> bool:

class Solution:
    def firstBadVersion(self, n: int) -> int:
        left, right = 1, n
        while left < right:
            mid = left + (right - left ) // 2
            if isBadVersion(mid):
                # 错误版本在 [left, mid] 这个闭区间
                right = mid
            else:
                left = mid + 1
        return left

33.搜索旋转排序数组

题目链接:https://leetcode.com/problems/search-in-rotated-sorted-array

解法:

二分查找,统一写左闭右闭区间:while循环时,left <= right。

这个题,麻烦还是在于条件判断时,要不要加 == 符号,比如有时候是 <=,有时候又是 < ,非常容易搞错。我暂时也没想到好的办法。

nums[mid] >= nums[left] 这个条件里加了等号,比如 [3,1],查找1,这个例子可以验证必须加等号。

以下是官方题解的图:

Grind75第8天 | 278.第一个错误的版本、33.搜索旋转排序数组、981.基于时间的键值存储_第1张图片

其他的参考题解。

参考题解:二分查找

边界条件:

时间复杂度:O(logn)

空间复杂度:O(1)

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        left, right = 0, len(nums) - 1
        while left <= right:
            mid = left + (right - left) // 2
            if nums[mid] == target:
                return mid
            # 注意这里有等号
            if nums[mid] >= nums[left]:
                # 正因为target不等于nums[mid],所以需要mid左移一位
                if target >= nums[left] and target < nums[mid]:
                    right = mid - 1
                else:
                    left = mid + 1
            else:
                # 正因为target > nums[mid] 而不是相等,所以mid肯定不是target的位置,而是右移一位,于是left = mid + 1
                if target > nums[mid] and target <= nums[right]:
                    left = mid + 1
                else:
                    right = mid - 1
        return -1

981.基于时间的键值存储

题目链接:https://leetcode.com/problems/time-based-key-value-store

解法:

这个题用二分查找。我们构造哈希map,值是一个列表,元素是pair对 [value, timestamp],由于timestamp是升序的,所以我们用二分查找来做。那么我们要找到小于等于目标timestamp的数中,最大的一个。

这个题很多题解都没有说明一个关键问题,那就是在二分查找中,如果target不在升序数组nums里面,那么查找结束时,left和right分别是代表什么。

举个例子 nums=[1,3,5,6,7],target=4,按照左闭右闭的二分查找写法,那么当left > right 时,查找结束,即 left=2, right = 1。可以看到 nums[left] 表示第一个大于target的数,而 nums[right] 表示小于target的数中,最大的一个。

所以这个题,如果调用get函数时,进行二分查找,如果目标timestamp不在哈希map中,那么查找结束时,left > right。如果left >0, right >=0,那么我们可以通过 left-1来获取答案,也可以通过right来获取答案。

参考题解:二分查找

边界条件:无

时间复杂度:O(logn)

空间复杂度:O(n)

class TimeMap:

    def __init__(self):
        self.map = {}

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

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

        kv_list = self.map[key]
        left, right = 0, len(kv_list) - 1
        while left <= right:
            mid = left + (right - left) // 2
            # 如果等于,那么直接返回
            if kv_list[mid][1] == timestamp:
                return kv_list[mid][0]

            if kv_list[mid][1] > timestamp:
                right = mid - 1
            else:
                left = mid + 1
        # 当left>right时,停止循环,那么[:right]区间是 < timestamp,[left:]区间是>timestamp的
        if left > 0:
            return kv_list[left-1][0]
        return ''           


# Your TimeMap object will be instantiated and called as such:
# obj = TimeMap()
# obj.set(key,value,timestamp)
# param_2 = obj.get(key,timestamp)

你可能感兴趣的:(数据结构和算法,算法,数据结构,leetcode)