目录
五、剑指 Offer 53 - I. 在排序数组中查找数字 I
一、剑指 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/