面试常考编程
双指针
两数之和
class Solution:# 字典 时间复杂度O(N) 空间复杂度O(N) 如果列表是排好序的可以使用双指针,时间复杂度O(N),空间复杂度为O(1)
def twoSum(self, nums: List[int], target: int) -> List[int]:
hashtable = dict()
for i, num in enumerate(nums):
if target - num in hashtable:
return [hashtable[target - num], i]
hashtable[nums[i]] = i
return []
#有序,双指针
left,right = 0,len(numbers) - 1
while left < right:
if numbers[left] + numbers[right] == target:
return [left+1,right+1]
elif numbers[left] + numbers[right] < target:
left+=1
else:
right-=1
二分
33. 搜索旋转排序数组(在无重复旋转数组上搜索一个target)
33. 搜索旋转排序数组(在无重复旋转数组上搜索一个target)
思路:二分,找到就返回,否则
1.左边递增,并且target在递增范围内,右边界更新
2.左边递增,target不在递增范围内,左边界更新
3.右边递增,target在递增范围内,左边界更新
4.右边递增,target不在递增范围内,右边界更新
class Solution:
def search(self, nums: List[int], target: int) -> int:
if not nums:return -1
left,right = 0,len(nums)-1
while left <= right:
mid = (left + right) // 2
if nums[mid] == target:
return mid
if nums[0] <= nums[mid]:#说明是递增的,没有旋转
if nums[0] <=target <nums[mid]:
right=mid-1
else:
left=mid+1
else:#说明0,mid之间有旋转,mid右边是递增的
if nums[mid]<target<=nums[len(nums) - 1]:
left = mid + 1
else:
right = mid - 1
return -1
153. 寻找旋转排序数组中的最小值(非重复)
如果当前的数比前一个数小,则该数为旋转,返回
如果当前数比第一个数大,左边界更新
否则,右边界更新
154.寻找旋转排序数组中的最小值 II(有重复)
如果当前数比右边界数小,更新右边界
否则如果当前数比右边界大,更新左边界
如果相等,右边界-1
50. Pow(x, n)(快速幂)
class Solution:#迭代方法,时间复杂度O(logn)空间复杂度O(1)
def myPow(self, x: float, n: int) -> float:
def quickMul(N):
ans = 1.0
# 贡献的初始值为 x
x_contribute = x
# 在对 N 进行二进制拆分的同时计算答案
while N > 0:
if N % 2 == 1:
# 如果 N 二进制表示的最低位为 1,那么需要计入贡献
ans *= x_contribute
# 将贡献不断地平方
x_contribute *= x_contribute
# 舍弃 N 二进制表示的最低位,这样我们每次只要判断最低位即可
N //= 2
return ans
return quickMul(n) if n >= 0 else 1.0 / quickMul(-n)
动态规划
300. 最长上升子序列
300. 最长上升子序列
如果当前数大于存的最后一个数,直接添加
否则查找当前数在结果的替换位置,替换一下,同时更新当前位置的dp为查找到的位置+1
根据dp矩阵反向找具体序列
import bisect
class Solution:#时间复杂度O(nlogn) 空间复杂度o(n)
def LIS(self , arr ):
n = len(arr)
dp = [1] * n
save = [arr[0]]
length = 1
for i in range(1,n):
if arr[i] > save[-1]:
save.append(arr[i])
length += 1
dp[i] = length
else:
index = bisect.bisect(save,arr[i])
dp[i] = index + 1
save[index] = arr[i]
max_num = save[-1]
max_index = arr.index(max_num)
res = []
for i in range(max_index,-1,-1):
if res==[] or (arr[i] < res[-1] and dp[i]==length):
res.append(arr[i])
length -=1
return res[::-1]
1143. 最长公共子序列(此题没要求具体序列,下面的代码可以返回具体序列)
1143. 最长公共子序列(此题没要求具体序列,下面的代码可以返回具体序列)
思路:生成(m+1)*(n+1)矩阵,即多了空字符
状态转移:
如果当前字符相等,dp[i][j]=dp[i-1][j-1]+1
不相等则,max(dp[i-1][j],dp[i][j-1])
根据dp结果找具体序列
class Solution:
def LCS(self , s1 , s2 ):
s1 = list(s1)
s2 = list(s2)
m = len(s1)
n = len(s2)
res = []
dp = [[0]*(n+1) for i in range(m+1)]
for i in range(1,m+1):
for j in range(1,n+1):
if s1[i-1] == s1[j-1]:
dp[i][j] = dp[i-1][j-1]+1
else:
dp[i][j] = max(dp[i-1][j],dp[i][j-1])
if dp[-1][-1] == 0:
return -1
i,j = m,n
while i > 0 and j > 0:
if s1[i-1] == s2[j-1]:
res.append(s1[i-1])
i-=1
j-=1
continue
else:
if dp[i][j-1] >= dp[i-1][j]:
j-=1
else:
i-=1
return ''.join(res[::-1])
最长公共子串(最长的连续的串,需要返回具体序列)
最长公共子串(最长的连续的串,需要返回具体序列)
思路:从头开始检查以当前字符结尾的串是否在另一个串出现,如果出现,max_len+1,不出现不更新,因为不出现的话,最远也只检查到当前最长的那个字符,例如
abcdefgre cdefgrefaa
检查到c时,最远也只检查到c即可
class Solution:
def LCS(self , str1 , str2 ):
if len(str1) > len(str2):
str1, str2 = str2, str1
max_len, res = 0, ''
for i in range(len(str1)):
if str1[i-max_len: i+1] in str2:
res = str1[i-max_len:i+1]
max_len += 1
if not res:
return -1
else:
return res
编辑距离
https://leetcode-cn.com/problems/edit-distance/solution/bian-ji-ju-chi-by-leetcode-solution/
本质不同的操作实际上只有三种:
在单词 A 中插入一个字符;
在单词 B 中插入一个字符;
修改单词 A 的一个字符
class Solution:
def minDistance(self, word1, word2):
n = len(word1)
m = len(word2)
# 有一个字符串为空串
if n * m == 0:
return n + m
# DP 数组
D = [ [0] * (m + 1) for _ in range(n + 1)]
# 边界状态初始化
for i in range(n + 1):
D[i][0] = i
for j in range(m + 1):
D[0][j] = j
# 计算所有 DP 值
for i in range(1, n + 1):
for j in range(1, m + 1):
left = D[i - 1][j] + 1
down = D[i][j - 1] + 1
left_down = D[i - 1][j - 1]
if word1[i - 1] != word2[j - 1]:
left_down += 1
D[i][j] = min(left, down, left_down)
return D[n][m]
bfs\dfs
数据流中的中位数
Python 中 heapq 模块是小顶堆。实现 大顶堆 方法: 小顶堆的插入和弹出操作均将元素 取反 即可
from heapq import *
class MedianFinder:
def __init__(self):
#时间 查找中位数O(1),添加数字O(logn) 空间复杂度O(n)
self.A=[]#小顶堆,最小数在头部,此存储的都是大的数,默认len(A)>=len(B)
self.B = []#大顶堆,最大数在头部,此存储的都是小的数
def addNum(self, num: int) -> None:
if len(self.A)!=len(self.B):#说明A里个数较多,因此需往B里添加一个数,而往B里添加的应该是一个较小的数,因此首先将num加入到A中,之后pop出A的头,push到B
heappush(self.B, -heappushpop(self.A, num))
else:#说明两个个数相等,需往A中添加数,由于A是大的数的集合,因此需往A中添加大的数,因此将数添加到B后,让Bpop出大的数,push到A
heappush(self.A, -heappushpop(self.B, -num))
def findMedian(self) -> float:
return self.A[0] if len(self.A) != len(self.B) else (self.A[0] - self.B[0]) / 2.0
检查是否为二叉搜索树的后序遍历序列
https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof/solution/mian-shi-ti-33-er-cha-sou-suo-shu-de-hou-xu-bian-6/
class Solution:
def verifyPostorder(self, postorder: [int]) -> bool:
stack, root = [], float("+inf")
for i in range(len(postorder) - 1, -1, -1):
if postorder[i] > root: return False
while(stack and postorder[i] < stack[-1]):
root = stack.pop()
stack.append(postorder[i])
return True
两个树节点的最近公共祖先
class Solution:
def lowestCommonAncestor(self, root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode:
if not root or root == p or root == q:return root
left = self.lowestCommonAncestor(root.left,p,q)
right = self.lowestCommonAncestor(root.right,p,q)
if left and right:return root
if left : return left
if right: return right
二叉树转双向链表
class Solution:
def treeToDoublyList(self, root: 'Node') -> 'Node':
def dfs(cur):
if not cur: return
dfs(cur.left) # 递归左子树
if self.pre: # 修改节点引用
self.pre.right, cur.left = cur, self.pre
else: # 记录头节点
self.head = cur
self.pre = cur # 保存 cur
dfs(cur.right) # 递归右子树
if not root: return
self.pre = None
dfs(root)
self.head.left, self.pre.right = self.pre, self.head
return self.head
中序后序构建二叉树
前序中序构建二叉树
每K个反转链表
递归回溯
排序
数组中第K大的数
链表排序