【图解算法数据结构】(六)查找算法

目录

一、剑指 Offer 03. 数组中重复的数字

1.1 题求

1.2 求解

二、剑指 Offer 04. 二维数组中的查找

2.1 题求

2.2 求解

2.3 解答

三、剑指 Offer 11. 旋转数组的最小数字 ☆

3.1 题求

3.2 求解

3.3 解答

四、剑指 Offer 50. 第一个只出现一次的字符

4.1 题求

4.2 求解

4.3 解答

五、剑指 Offer 53 - I. 在排序数组中查找数字 I

1.1 题求

1.2 求解

1.3 解答

六、剑指 Offer - II. 0~n-1 中缺失的数字

6.1 题求

6.2 求解

6.3 解答


一、剑指 Offer 03. 数组中重复的数字

1.1 题求

1.2 求解

法一:哈希集合

# 36ms - 90.77%
class Solution:
    def findRepeatNumber(self, nums: List[int]) -> int:
        repeat = set()
        for n in nums:
            if n in repeat:
                return n
            repeat.add(n)

法二:哈希表

# 36ms - 97.00%
class Solution:
    def findRepeatNumber(self, nums: List[int]) -> int:
        count = [0] * len(nums)
        for n in nums:
            if count[n] > 0:
                return n
            count[n] += 1

官方说明

class Solution:
    def findRepeatNumber(self, nums: [int]) -> int:
        dic = set()
        for num in nums:
            if num in dic: return num
            dic.add(num)
        return -1

# 40ms - 91.91%
class Solution:
    def findRepeatNumber(self, nums: [int]) -> int:
        i = 0
        while i < len(nums):
            # 正好在位置上, 继续推进
            if nums[i] == i:
                i += 1
                continue
            # 重复
            if nums[nums[i]] == nums[i]: 
                return nums[i]
            # 交换
            nums[nums[i]], nums[i] = nums[i], nums[nums[i]]
        return -1

参考资料:

《剑指 Offer 第二版》

https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/59bjss/

https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/59jyzd/


二、剑指 Offer 04. 二维数组中的查找

2.1 题求

2.2 求解

法一:二分搜索

# 32ms - 96.22%
class Solution:
    def findNumberIn2DArray(self, matrix: List[List[int]], target: int) -> bool:
        
        def bisect(arr, left, right):
            ''' 二分查找 '''
            while left <= right:
                mid = (left + right) // 2
                if arr[mid] < target:
                    left = mid + 1
                elif arr[mid] > target:
                    right = mid - 1
                else:  # arr[mid] == target:
                    return True
            return False   
        
        # 行数  
        m = len(matrix) 
        if not m:  # m == 0
            return False
        
        # 列数
        n = len(matrix[0]) 
        if not n:  # n == 0
            return False
        
        # 行选择
        for i in range(m):
            if target < matrix[i][0]:  # 比第 i 行的最小值还要小, 说明目标不存在
                return False
            elif target > matrix[i][-1]:  # 比第 i 行的最大值还要大, 直接跳过搜素
                continue
            if bisect(matrix[i], 0, n-1):  # 第 i 行范围正常, 二分查找
                return True
        return False

官方说明 

# 32ms - 96.22%
class Solution:
    def findNumberIn2DArray(self, matrix: List[List[int]], target: int) -> bool:
        # 从矩阵左下角开始遍历 !!! (最重要的起点, 同理右上角也可以作为起点, 规则相反)
        i, j = len(matrix)-1, 0  # 行、列起点
        while i >= 0 and j < len(matrix[0]):
            if matrix[i][j] > target:  # target 可能在当前位置上方, 走过第 i 行元素
                i -= 1
            elif matrix[i][j] < target:  # target 可能在当前位置右侧, 走过第 j 列元素
                j += 1
            else:  # 找到 target
                return True
        return False

2.3 解答

参考资料:

《剑指 Offer 第二版》

https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/5v76yi/

https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/5vl81e/


三、剑指 Offer 11. 旋转数组的最小数字 ☆

3.1 题求

3.2 求解

测试用例

[1, 2, 3, 4, 5]
[2, 3, 4, 5, 1]
[3, 4, 5, 1, 2]
[4, 5, 1, 2, 3]
[5, 1, 2, 3, 4] 
[0, 1, 2, 2, 2] 
[2, 0, 1, 2, 2] 
[2, 2, 0, 1, 2] 
[2, 2, 2, 0, 1] 
[1, 2, 2, 2, 0]
[1]
[0, 1]
[1, 0]
[1, 1]
[1, 1, 1]
[10, 1, 10, 10, 10]
[6, 1, 6, 6, 6, 6, 6]
[6, 6, 6, 6, 6, 1, 6]

法一:二分搜索 + 线性搜索

# 32ms - 87.32%
class Solution:
    def minArray(self, numbers: List[int]) -> int:
        left = 0
        right = len(numbers)-1
        while left < right:
            mid = (left + right) // 2     
            if numbers[mid] > numbers[right]:  # min 在 mid+1, right
                left = mid + 1
            elif numbers[mid] < numbers[right]:  # min 在 left, mid
                right = mid
            else:  # min 不确定在哪里, 直接线性搜索  left, right-1 或 left+1, right
                return min(numbers[left:right])
        return numbers[left]

官方说明

class Solution:
    def minArray(self, numbers: [int]) -> int:
        i, j = 0, len(numbers) - 1
        while i < j:
            m = (i + j) // 2  # // 会向下取整, 从而有:i <= m < j  
            if numbers[m] > numbers[j]: 
                i = m + 1
            elif numbers[m] < numbers[j]: 
                j = m
            else: 
                j -= 1
        return numbers[i]

class Solution:
    def minArray(self, numbers: [int]) -> int:
        i, j = 0, len(numbers)-1
        while i < j:
            m = (i + j) // 2
            if numbers[m] > numbers[j]: 
                i = m + 1
            elif numbers[m] < numbers[j]: 
                j = m
            else: 
                return min(numbers[i:j])
        return numbers[i]

3.3 解答

参考资料:

《剑指 Offer 第二版》

https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/50xofm/

https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/5055b1/


四、剑指 Offer 50. 第一个只出现一次的字符

4.1 题求

4.2 求解

法一:哈希表

# 116ms - 43.72%
class Solution:
    def firstUniqChar(self, s: str) -> str:
        # 时间复杂度至少是 O(n) 因为直到遍历完最后一个才可能确定首个单字符
        # 所以优化的关键在于遍历完所有字符后以最快速度确定首个单字符
        
        # 字频哈希表
        hashmap = {}  # key-char : value-nums
        
        # 字符出现次数计数
        for c in s:
            if hashmap.get(c) is None:
                hashmap[c] = 1
            else:
                hashmap[c] += 1
                
        # 搜索首个单字符
        for c in s:
            if hashmap[c] == 1:
                return c
        
        # 没有不重复的首个单字符
        return " "

官方说明

# 64ms - 97.39
class Solution:
    def firstUniqChar(self, s: str) -> str:
        dic = {}
        for c in s:
            dic[c] = not c in dic  # 使用 True 和 Fasle 状态代替计数, 好特别多
        for c in s:
            if dic[c]: 
                return c
        return ' '

# 68ms - 95.15%
class Solution:
    def firstUniqChar(self, s: str) -> str:
        dic = collections.OrderedDict()  # {}
        for c in s:
            dic[c] = not c in dic
        for k, v in dic.items():
            if v: 
                return k
        return ' '

4.3 解答

参考资料:

《剑指 Offer 第二版》

https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/5viisg/

https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/5vu0zv/


五、剑指 Offer 53 - I. 在排序数组中查找数字 I

1.1 题求

1.2 求解

法一:二分搜索 + 线性搜素

# 32ms - 80.64%
class Solution:
    def search(self, nums: List[int], target: int) -> int:
        # target 计数器
        summ = 0
        
        # 二分搜索找到 target 作为起点
        n = len(nums)
        left = 0
        right = n - 1
        while left <= right:
            mid = (left + right) // 2
            if nums[mid] > target:
                right = mid - 1
            elif nums[mid] < target:
                left = mid + 1
            else:
                summ += 1
                break
        
        # 因越界而退出循环, 计数为 0
        if left > right:
            return summ
        
        # 因找到目标而退出循环, 开始计数
        # 向右
        rhs = mid + 1
        while rhs < n and nums[rhs] == target:
            summ += 1  # 计数加一
            rhs += 1  # 右移一步
        # 向左
        lhs = mid - 1
        while lhs > -1 and nums[lhs] == target:
            summ += 1  # 计数加一
            lhs -= 1  # 左移一步
        
        return summ

官方说明 

# 20ms - 99.86%
class Solution:
    def search(self, nums: [int], target: int) -> int:
        # 搜索右边界 right
        i, j = 0, len(nums) - 1
        while i <= j:
            m = (i + j) // 2
            if nums[m] <= target:  # 右边界
                i = m + 1
            else: 
                j = m - 1
        right = i
        
        # 若数组中无 target ,则提前返回
        if j >= 0 and nums[j] != target: 
            return 0
        
        # 搜索左边界 left
        i = 0
        while i <= j:
            m = (i + j) // 2
            if nums[m] < target:  
                i = m + 1
            else:  # nums[m] >= target  # 左边界 
                j = m - 1
        left = j
        
        return right - left - 1

# 28ms - 93.85%
class Solution:
    def search(self, nums: [int], target: int) -> int:
        def helper(tar):
            i, j = 0, len(nums)-1
            while i <= j:
                m = (i + j) // 2
                if nums[m] <= tar:  # 寻找右边界
                    i = m + 1
                else: 
                    j = m - 1
            return i
        # 右边界作差, 思路非常好
        return helper(target) - helper(target-1)

1.3 解答

参考资料:

《剑指 Offer 第二版》

https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/5874p1/

https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/58lgr7/


六、剑指 Offer - II. 0~n-1 中缺失的数字

6.1 题求

6.2 求解

法一:二分搜索

# 32ms - 93.44%
class Solution:
    def missingNumber(self, nums: List[int]) -> int:
        m = len(nums)  # m+1=n个数字中, 每个数字都在范围0~n-1之内
        
        # 首尾缺失检查
        if nums[0] != 0:
            return 0
        elif nums[-1] != m:  # nums[m-1]
            return m
        
        # 中间缺失检查
        left = 0
        right = m - 1
        while left < right:  # left == right 时退出, 此时索引在缺失值的位置上
            mid = (left + right) // 2
            # case1: nums[mid] 过大, 说明缺失值在中、左侧
            if nums[mid] > mid:
                right = mid
            # case2: nums[mid] 正常, 说明缺失值在右侧
            elif nums[mid] == mid:
                left = mid + 1
                
        return right  # or return left

简化

# 32ms - 93.44%
class Solution:
    def missingNumber(self, nums: List[int]) -> int:
        left = 0
        right = len(nums) - 1
        while left <= right: 
            mid = (left + right) // 2
            if nums[mid] > mid:
                right = mid - 1
            elif nums[mid] == mid:
                left = mid + 1
                
        return left

官方说明 

# 32ms - 92.44%
class Solution:
    def missingNumber(self, nums: List[int]) -> int:
        i, j = 0, len(nums)-1
        while i <= j:
            m = (i + j) // 2
            if nums[m] == m: 
                i = m + 1
            else: 
                j = m - 1
        return i

6.3 解答

参考资料:

《剑指 Offer 第二版》

https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/58iqo5/

https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/58ut96/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值