参加了下2023届秋招,不得不感叹leetcode&lintcode的题目实在太多了(也很难),特别对于我这种非科班生而言,感觉有必要分类整理一下,以便以后在工作中更好的应用。花了点时间刷了下二分查找的相关题目,刷的越多,反而越不会了,先把目前遇到的几个题目总结下吧。
基于索引(定义域)的类型的题目一般规律是:给定一维升序数组和目标值target,寻找等于或小于(大于)目标值的索引位置。
简单题目是判断目标值存在与否;
中等难度题目是判断目标值存在的左右边界,左边界需要查找的是第一个大于等于target的值索引,右边界需要查找的是第一个大于target的值索引减1,进一步根据左右边界构成的闭区间是否存在判断目标边界情况;
再难一点就是理解目标值起到将升序数组一分为二的效果:左侧数组小于目标值,右侧数组大于等于目标值,相当于是把二分法作为解题的第一步,以此为基础,再进行第二步算法及第三步算法等,常见于几种算法混合求解的困难题目。
leetcode 704. 二分查找
该题在网上的解答有很多,强烈推荐代码随想录的讲解,条理清晰。
from typing import List
'''
704. 二分查找
题目描述:给定一个n个元素有序的(升序)整型数组nums和一个目标值target,写一个函数搜索nums中的target,如果目标值存在返回下标,否则返回 -1。
示例 1:
输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4
题眼:有序数组且无重复(有序是二分法的前提)
思路:二分法,[left, right]左闭右闭区间,用middle更新左右边界,难点是准确把握循环终止时的状态是left>right。
'''
class Solution:
def search(self, nums: List[int], target: int) -> int:
# 情况1:target小于nums[0]、target大于nums[-1](提示里有n>=1说明数组不为空)
if target < nums[0] or target > nums[len(nums)-1]:
return -1
# 情况2:traget==nums[mid],使用二分法
left, right = 0, len(nums) - 1
while left <= right: # [left, right],当left==right是有意义的
middle = left + (right - left) // 2
# [left, right] 决定了middle区间的更新原则和while循环条件
if nums[middle] > target:
right = middle - 1 # [left, middle-1]
elif nums[middle] < target:
left = middle + 1 # [middle+1, right]
else:
return middle
# 情况3:以上情况全不满足(target范围在数组中间,但不存在target),此时right+1=left
return -1
if __name__ == '__main__':
obj = Solution()
while True:
try:
in_line = input().strip().split('=')
nums = [int(n) for n in in_line[1].split('[')[1].split(']')[0].split(',')]
target = int(in_line[2])
print(obj.search(nums, target))
except EOFError:
break
leetcode 35. 搜索插入位置
from typing import List
'''
35. 搜索插入位置
题目描述:给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n) 的算法。
示例 1:
输入: nums = [1,3,5,6], target = 5
输出: 2
题眼:有序数组且无重复(有序是二分法的前提), O(log n) 的算法。
思路:“704. 二分查找” 的升级版,目标不存在时返回的不是-1了,要返回顺序插入的位置。
'''
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
# 情况1:target小于nums[0](提示里有n>=1说明数组不为空)
if target < nums[0]:
return 0
# 情况2:target大于nums[-1]
if target > nums[len(nums)-1]:
return len(nums)
# 情况3:traget==nums[mid],使用二分法
left, right = 0, len(nums) - 1
while left <= right: # [left, right],当left==right是有意义的
middle = left + (right - left) // 2
# [left, right] 决定了middle区间的更新原则和while循环条件
if nums[middle] > target:
right = middle - 1 # [left, middle-1]
elif nums[middle] < target:
left = middle + 1 # [middle+1, right]
else:
return middle
# 情况4:以上情况全不满足(target范围在数组中间,但不存在target),此时right+1=left,要返回顺序插入的位置
return right + 1 # 此时,要清楚掌握二分法退出循环时的left、right取值情况
if __name__ == "__main__":
obj = Solution()
while True:
try:
in_line = input().strip().split('=')
nums = [int(n) for n in in_line[1].split('[')[1].split(']')[0].split(',')]
target = int(in_line[2])
print(obj.searchInsert(nums, target))
except EOFError:
break
leetcode 34. 在排序数组中查找元素的第一个和最后一个位置
这个题目有点难度了,当时做这个题目花了好久时间,推荐代码随想录的讲解。
from typing import List
'''
658. 找到 K 个最接近的元素
题目描述:给定一个 排序好 的数组arr ,两个整数 k 和 x ,从数组中找到最靠近 x(两数之差最小)的 k 个数。返回的结果必须要是按升序排好的。
整数 a 比整数 b 更接近 x 需要满足:
|a - x| < |b - x| 或者
|a - x| == |b - x| 且 a < b
示例 1:
输入:arr = [1,2,3,4,5], k = 4, x = 3
输出:[1,2,3,4]
题眼:有序数组
思路:(有点难度,看了官方解答才明白)二分法找到分界点,左侧小于x,右侧大于等于x;
然后 从分界点开始向左右两侧取最接近的k个值
'''
import bisect
class Solution:
def findClosestElements(self, arr: List[int], k: int, x: int) -> List[int]:
# 第一步,将数组一分为二,左侧位置[0,left]元素全部小于x,右侧位置[right,len(arr)-1]元素全部大于等于x
right = bisect.bisect_left(arr, x) # 返回第一个大于等于x的元素索引 0~len(arr)
left = right - 1
# right = self.binarySearchLeftBorder(arr, x)
# 第二步,此时left和right位置分别对应各自部分最接近x的元素(在left和right有效时)
for _ in range(k):
if left < 0: # 左侧已经到达边界,更新right指针
right += 1
elif right > len(arr) - 1: # 右侧已经到达边界,更新left指针
left -= 1
elif x - arr[left] <= arr[right] - x: # 左侧值属于答案范围,更新left指针
left -= 1
elif x - arr[left] > arr[right] - x: # 右侧值属于答案范围,更新right指针
right += 1
# 此时得到答案区间[left+1: right-1],为了取到右侧边界,最终返回
return arr[left+1: right]
def binarySearchLeftBorder(self, arr: List[int], x: int) -> int:
if x < arr[0]:
return 0
elif x > arr[len(arr)-1]:
return len(arr)
else:
left, right = 0, len(arr) - 1
while left <= right:
mid = left + (right - left) // 2
if arr[mid] >= x: # 寻找左边界,用right指针,所以等于时更新right
right = mid - 1
elif arr[mid] < x:
left = mid + 1
return left
if __name__ == '__main__':
obj = Solution()
while True:
try:
in_line = input().strip().split('=')
nums = [int(n) for n in in_line[1].split('[')[1].split(']')[0].split(',')]
k = int(in_line[2].split(',')[0])
x = int(in_line[3])
# print(nums, k, x)
print(obj.findClosestElements(nums, k, x))
except EOFError:
break
leetcode 658. 找到 K 个最接近的元素
from typing import List
'''
658. 找到 K 个最接近的元素
题目描述:给定一个 排序好 的数组arr ,两个整数 k 和 x ,从数组中找到最靠近 x(两数之差最小)的 k 个数。返回的结果必须要是按升序排好的。
整数 a 比整数 b 更接近 x 需要满足:
|a - x| < |b - x| 或者
|a - x| == |b - x| 且 a < b
示例 1:
输入:arr = [1,2,3,4,5], k = 4, x = 3
输出:[1,2,3,4]
题眼:有序数组
思路:(有点难度,看了官方解答才明白)二分法找到分界点,左侧小于x,右侧大于等于x;
然后 从分界点开始向左右两侧取最接近的k个值
'''
import bisect
class Solution:
def findClosestElements(self, arr: List[int], k: int, x: int) -> List[int]:
# 第一步,将数组一分为二,左侧位置[0,left]元素全部小于x,右侧位置[right,len(arr)-1]元素全部大于等于x
right = bisect.bisect_left(arr, x) # 返回第一个大于等于x的元素索引 0~len(arr)
left = right - 1
# right = self.binarySearchLeftBorder(arr, x)
# 第二步,此时left和right位置分别对应各自部分最接近x的元素(在left和right有效时)
for _ in range(k):
if left < 0: # 左侧已经到达边界,更新right指针
right += 1
elif right > len(arr) - 1: # 右侧已经到达边界,更新left指针
left -= 1
elif x - arr[left] <= arr[right] - x: # 左侧值属于答案范围,更新left指针
left -= 1
elif x - arr[left] > arr[right] - x: # 右侧值属于答案范围,更新right指针
right += 1
# 此时得到答案区间[left+1: right-1],为了取到右侧边界,最终返回
return arr[left+1: right]
def binarySearchLeftBorder(self, arr: List[int], x: int) -> int:
if x < arr[0]:
return 0
elif x > arr[len(arr)-1]:
return len(arr)
else:
left, right = 0, len(arr) - 1
while left <= right:
mid = left + (right - left) // 2
if arr[mid] >= x: # 寻找左边界,用right指针,所以等于时更新right
right = mid - 1
elif arr[mid] < x:
left = mid + 1
return left
if __name__ == '__main__':
obj = Solution()
while True:
try:
in_line = input().strip().split('=')
nums = [int(n) for n in in_line[1].split('[')[1].split(']')[0].split(',')]
k = int(in_line[2].split(',')[0])
x = int(in_line[3])
# print(nums, k, x)
print(obj.findClosestElements(nums, k, x))
except EOFError:
break
个人总结体会
二分法从宏观思想上来说是将一个有序序列对半查找,落实到具体的题目里掌握其关键点:准确把握循环终止时的状态,即left和right指针的相对位置关系(左闭右闭区间法则下是left-right=1),从而能够轻松应对和处理确定等于(小于或大于)某个target值在有序序列的边界位置,像"leetcode 658. 找到 K 个最接近的元素"这种复合型的题目,第一步用二分法找到二分位置、第二步用双指针寻找答案区间就很难想到对应解法,只能不断练习…