导言
笔者最近在找实习,面试了一部分公司,靠着吃老本和刷几道 LeetCode 题目,在面试中苟延残喘,最痛苦的还是在线手撕代码,那个刺激劲仍历历在目。
在面试过程中,发现经常问到或者手撕的类型有:查找算法、排序算法(复杂度、稳定性等)、树(前中后遍历:递归和非递归实现)
在面试的时候,有些面试官会使劲给你提示,其实想表达一个信号,你第一时间没想到方案没事,如果能够在面试官的提示下把问题给解决了,那么你的能力应该也不差。
个人有几点体会:
- 表达的时候逻辑一定要清晰
- 第一次想到的方案不一定要是最优的,起码可以告诉面试官,我能够第一时间想到方案
- 在进行算法优化的时候,面试官可能会提醒你(复杂度怎么优化,所以要记住一些常见复杂度的顺序和算法)兴许可以从中找到突破口
给自己总结一个初始模板吧,以后慢慢优化
我第一个想到的方案是 xx 算法,该算法的复杂度是 xx,思路是 xxx 我还有一个复杂度更低的算法 xxx,思路是 xxx
无他,多刷题吧~
今天就来看一道有关查找类型的题目
二分查找
适合场景
- 二分查找依赖的是顺序表结构,简单点说就是数组
- 二分查找针对的是有序数据。
- 数据量太小不适合二分查找。
- 数据量太大也不适合二分查找。
写二分算法的注意点
- 循环退出条件
- mid 的取值(防止溢出)
- low 和 high 的更新
常规二分法
- 循环写法
from typing import List
# 循环写法
def binary_search(nums: List[int], target: int) -> int:
"""
nums:一个数字列表
target:目标值
return:目标值在列表中的索引,
"""
if len(nums) == 0:
return -1
low = 0
high = len(nums)-1
while low<=high: # 小于等于
mid = low + ((high-low) >> 1) # 取整,移位更高效,防止溢出
if nums[mid] == target:
return mid
elif nums[mid] > target:
high = mid-1 #
elif nums[mid] < target:
low = mid+1 #
return -1
- 递归写法
from typing import List
# 递归写法
def binary_search(nums: List[int],low: int, high: int, target: int) -> int:
if low > high:
return -1
mid = low + ((high-low) >> 1) # 取整,移位更高效,防止溢出
if nums[mid] == target:
return mid
elif nums[mid] > target:
return binary_search(nums, low, mid-1, target)
elif nums[mid] < target:
return binary_search(nums, mid+1, high, target)
二分查找的变形
二分查找的变形有很多,常见的二分查找变形问题有
进阶题目
我们来看一道中等难度的二分查找相关的问题
LeetCode:540. 有序数组中的单一元素
问题
给定一个只包含整数的有序数组,每个元素都会出现两次,唯有一个数只会出现一次,找出这个数。 示例 1: 输入: [1,1,2,3,3,4,4,8,8] 输出: 2 示例 2: 输入: [3,3,7,7,10,11,11] 输出: 10 注意: 您的方案应该在 O (log n) 时间复杂度和 O (1) 空间复杂度中运行。
解法
对于这道题,首先可以想到位运算的方法,但是该方法是 O(n) 复杂度
位运算解法
如果这道题没有要求是在 O(log n ) 复杂度下,那么可以使用位运算
- 异或运算满足交换律
- 相同的数异或为 0
- 0 与一个数异或是本身
class Solution:
def singleNonDuplicate(self, nums: List[int]) -> int:
temp = nums[0]
for i in nums[1:]:
temp ^= i
return temp
二分查找
关键词:有序
那如何判断我们想要的结果在二分之后的哪一部分呢?
一次面试时面试官提醒我说,为什么每个题目会给出示例呢,一般示例是很足够代表性的,不是随随便便选取的。
那我们就用示例来分析一下:
下表第一行是编号
- 找到第一次二分的中点,mid = (0+8)/2 = 4,就是 3
- 此时判断该值与前面的值是否相同,如果相同,我们计算一下 mid 之后的数组长度,如果是偶数,那么我们要找的值肯定在 mid-1 之前,反之。
- 如果 mid 的值与后面的值相同呢?那我们就计算 mid+1 之后的数组长度,如果是偶数,那么我们要找到的值在 mid 之前。
- 总之,我们以 mid 之后的数组长度的奇偶情况(如果mid的值与 mid-1 的值相同)或者 mid+1之后的数组长度的奇偶情况(如果mid的值与 mid-1 的值相同)
以下两种写法是一样的,只不过是在 while 里面判断 low==high 的情况还是在 while 外面判断
class Solution:
def singleNonDuplicate(self, nums: List[int]) -> int:
length = len(nums)
low = 0
high = length-1
while low < high:
mid = low+(high-low)//2
flag = (high - mid)%2 == 0 # 判断 mid 之后的数组长度
if nums[mid] == nums[mid-1]: # mid和前面的值相同
if flag: # mid 之后的数组长度是偶数
high = mid - 2
else: #
low = mid + 1
elif nums[mid] == nums[mid+1]: # mid 后面值的相同
if flag: # 偶数,但实际上是奇数,往后找我们要的数
low = mid + 2
else:
high = mid - 1
else:
return nums[mid]
return nums[low]
class Solution:
def singleNonDuplicate(self, nums: List[int]) -> int:
length = len(nums)
low = 0
high = length-1
while low <= high:
if low == high:
return nums[high]
mid = low+(high-low)//2
flag = (high - mid)%2 == 0 # 判断 mid 之后的数组长度
if nums[mid] == nums[mid-1]: # mid和前面的值相同
if flag: # mid 之后的数组长度是偶数
high = mid - 2
else: #
low = mid + 1
elif nums[mid] == nums[mid+1]: # mid 后面值的相同
if flag: # 偶数,但实际上是奇数,往后找我们要的数
low = mid + 2
else:
high = mid - 1
else:
return nums[mid]
如何学习数据结构与算法
推荐极客时间的这门课程,可以巩固一下知识。