高频题
链表的反转
class LinkedNode:
def __init__(self, val = 0, next = None):
self.val = val
self.next = next
def reverseList(head: LinkedNode) -> LinkedNode:
temp = None # 保存cur的下一个节点
cur = head
pre = None
while cur:
temp = cur.next
cur.next = pre # 翻转操作
pre = cur # 更新pre 和 cur指针
cur = temp
return pre
def printLinkedList(head: LinkedNode):
cur = head
while cur:
print(cur.val, end = " ")
cur = cur.next
print()
if __name__ == "__main__":
while True:
try:
# 输入5 1 2 3 4 5,表示链表有5个节点,值分别为1 2 3 4 5
# input()从标准输入中读取字符串。去掉尾随换行符。
# split()按空格分割成一个字符串列表
# map(int, ...): 将这个字符串列表中的每个元素转换为整数,返回一个 map 对象。
#n, *nums = ...: 解包 map 对象中的元素。第一个元素赋给 n,其余的元素作为列表赋给 nums。
s=input().split()
print(s)
n, *nums = map(int, input().split())
except:
break
if n == 0:
print("list is empty")
continue
dummyHead = LinkedNode(0) # 这里定义的头结点 是一个虚拟头结点,而不是真正的链表头结点
cur = dummyHead
for i in range(n): # 开始构造节点
cur.next = LinkedNode(nums[i])
cur = cur.next
printLinkedList(dummyHead.next) # 打印链表
printLinkedList(reverseList(dummyHead.next)) # 打印翻转后的链表
旋转排序数组中最小值
https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array/solutions/1257506/acm-xuan-shou-tu-jie-leetcode-xun-zhao-x-vf7b/?envType=study-plan-v2&envId=top-100-liked
class Solution:
def findMin(self, nums):
# 如果数组未旋转(第一个元素小于等于最后一个元素)
if nums[0] <= nums[-1]:
return nums[0] # 直接返回第一个元素,因为数组是升序的
l, r = 0, len(nums) - 1 # 初始化左右指针
# 使用二分查找法
while l < r:
mid = (l + r) // 2 # 计算中间位置
# 如果中间元素大于等于第一个元素,最小值在右半部分
if nums[mid] >= nums[0]:
l = mid + 1 # 移动左指针到 mid+1
else:
# 否则,最小值在左半部分或是 mid 本身
r = mid # 移动右指针到 mid
return nums[l] # 返回最小值所在位置的元素
最长回文字符串
- 多维dp,穷举子串长度
class Solution:
def longestPalindrome(self, s: str) -> str:
n=len(s)
# 没有判断小于2的情况
if n<2:
return s
st=0
l=1
# 二维数组的初始化
dp=[[False]*n for _ in range(n)]
for i in range(n):
dp[i][i]=True
for length in range(2,n+1):
for begin in range(n-length+1):
if length==2:
dp[begin][begin+1]=(s[begin]==s[begin+1])
else:
dp[begin][begin+length-1]=dp[begin+1][begin+length-2] and s[begin]==s[begin+length-1]
if dp[begin][begin+length-1] and length>l:
l=length
st=begin
return s[st:st+l]# 字符串的切片
最长递增子序列
https://programmercarl.com/0300.%E6%9C%80%E9%95%BF%E4%B8%8A%E5%8D%87%E5%AD%90%E5%BA%8F%E5%88%97.html#%E5%85%B6%E4%BB%96%E8%AF%AD%E8%A8%80%E7%89%88%E6%9C%AC
3.
浅拷贝和深拷贝
对数组中的元素等不可变类型,没有什么影响
矩阵
螺旋矩阵
螺旋矩阵中间过程需要检查是否已经检查完了所有行和列
旋转图像
特别要注意奇数(n+1)//2的情况
合并区间
1:没有想清楚排序后,应该两两进行合并
链表
反转:一定要注意dummyHead和head需要断开,否则会形成环形链表
def reverseList(head: LinkedNode) -> LinkedNode:
dummyHead = LinkedNode(0) # 定义一个虚拟头结点
# dummyHead.next = head
cur = head
while cur:
temp = cur.next
cur.next = dummyHead.next
dummyHead.next = cur
cur = temp
return dummyHead.next
环形链表 II
用hash表
快慢指针
先检测是否有环,然后再判断环入口的位置
链表排序
自顶向上:递归
边界值:空节点/一个节点
递归逻辑:找到中间节点
自底向上
二分查找(循环不变量及跳出循环的条件)
求有序数组中和target绝对值差最小的数,如果2个一样返回大的数
前缀和+defaultdict
和为K的子数组
1、为什么前缀和需要用pre[0]=1
https://leetcode.cn/problems/subarray-sum-equals-k/solutions/1447027/python3-by-wu-qiong-sheng-gao-de-qia-non-w6jw/?envType=study-plan-v2&envId=top-100-liked
TopK
从arr[1, n]这n个数中,找出最大的k个数,这就是经典的TopK问题。
一、排序
分析:明明只需要TopK,却将全局都排序了,这也是这个方法复杂度非常高的原因。那能不能不全局排序,而只局部排序呢?这就引出了第二个优化方法。
二、局部排序
冒泡,将全局排序优化为了局部排序,非TopK的元素是不需要排序的,节省了计算资源。不少朋友会想到,需求是TopK,是不是这最大的k个元素也不需要排序呢?这就引出了第三个优化方法。
def BubbleSort(arr):
n = len(arr)
for i in range(n):
# (n-i-1)这个点需要特别注意下,-1是下面利用的是arr[j] 和arr[j+1]进行比较
# -i是因为每经过一轮,已经有一个最大的元素到达了其最终的位置
for j in range(0, n-i-1):
if arr[j] > arr[j+1]:
arr[j], arr[j+1] = arr[j+1], arr[j]
return arr
def BubbleSortSolveTopk(arr,k):
n = len(arr)
for i in range(k):
for j in range(0, n-i-1):
if arr[j] > arr[j+1]:
arr[j], arr[j+1] = arr[j+1], arr[j]
return arr[n-k:]
堆
def Heapify(arr, n, i):
largest = i
l = 2*i + 1
r = 2*i + 2
if l < n and arr[l] > arr[largest]:
largest = l
if r < n and arr[r] > arr[largest]:
largest = r
if largest != i:
arr[i], arr[largest] = arr[largest], arr[i]
Heapify(arr, n, largest)
def HeapSortSolveTopk(arr,k):
n = len(arr)
for i in range(k//2 - 1, -1, -1):
Heapify(arr, k, i)
for i in range(k, n):
if arr[i] > arr[0]:
arr[0], arr[i] = arr[i], arr[0]
Heapify(arr, k, 0)
return arr[:k]
犯了浅拷贝的错误
class Solution:
def heapify(self,nums,n,i):
largest=i
l = 2*i+1
r = 2*(i+1)
if l<n and nums[l]>nums[largest]:
largest=l
if r<n and nums[r]>nums[largest]:
largest=r
if largest!=i:
nums[i],nums[largest]=nums[largest],nums[i]
self.heapify(nums,n,largest)
def heapsort(self,nums,k):
n=len(nums)
for i in range(n//2-1,-1,-1):
self.heapify(nums,n,i)
print(nums)
for i in range(k):
nums[0],nums[n-1-i]=nums[n-1-i],nums[0]
# 这部分是浅拷贝,因为后面要修改nums,所以不能直接用nums[:n-1-i]
self.heapify(nums,n-1-i,0)
print(nums)
return nums[n-k:]
用heapq头函数进行实现:最小K个数
Python 语言中的堆为小根堆,因此我们要对数组中所有的数取其相反数,才能使用小根堆维护前 k 小值。
import heapq
from typing import List
class Solution:
def smallestK(self, arr: List[int], k: int) -> List[int]:
if k == 0:
return list()
hp = [-x for x in arr[:k]]
heapq.heapify(hp)
for i in range(k, len(arr)):
if -hp[0] > arr[i]:
heapq.heappop(hp)
heapq.heappush(hp, -arr[i])
ans = [-x for x in hp]
return ans
分析
堆,将冒泡的TopK排序优化为了TopK不排序,节省了计算资源
快排思想
class Solution:
def partition(self, nums, l, r):
pivot = nums[r]
i = l - 1
for j in range(l, r):
if nums[j] <= pivot:
i += 1
nums[i], nums[j] = nums[j], nums[i]
nums[i + 1], nums[r] = nums[r], nums[i + 1]
return i + 1
def randomized_partition(self, nums, l, r):
i = random.randint(l, r)
nums[r], nums[i] = nums[i], nums[r]
return self.partition(nums, l, r)
def randomized_selected(self, arr, l, r, k):
pos = self.randomized_partition(arr, l, r)
num = pos - l + 1
if k < num:
self.randomized_selected(arr, l, pos - 1, k)
elif k > num:
self.randomized_selected(arr, pos + 1, r, k - num)
def smallestK(self, arr: List[int], k: int) -> List[int]:
if k == 0:
return list()
self.randomized_selected(arr, 0, len(arr) - 1, k)
return arr[:k]
快速排序
def quicksort(arr):
if not arr:
return []
def partition(arr,l,r):
pivot=arr[l]
while l<r:
while l<r and arr[r]>=pivot:
# 为什么这个地方是>=呢?其实都可以,主要是看相等的时候怎么进行处理
r-=1
arr[l]=arr[r]
while l<r and arr[l]<=pivot:
l+=1
arr[r]=arr[l]
arr[r]=pivot
return l
# 思考为什么要先从枢轴的反方向开始找,而不是从枢轴的方向开始找
# 这个是由算法的逻辑决定的,相当于把枢轴这个位置空出来,来存放合适的值,最后再把枢轴元素放在其合适的位置
def quicksort_helper(arr, l, r):
if l < r:
p = partition(arr, l, r)
quicksort_helper(arr, l, p - 1)
quicksort_helper(arr, p + 1, r)
quicksort_helper(arr, 0, len(arr) - 1)
return arr
另外一种partition的思路
def partition(self, nums, l, r):
pivot = nums[r]
# 用于跟踪小于等于枢轴的元素的最后一个位置
i = l - 1
for j in range(l, r):
if nums[j] <= pivot:
i += 1
nums[i], nums[j] = nums[j], nums[i]
nums[i + 1], nums[r] = nums[r], nums[i + 1]
return i + 1
kth largest element
内置函数进行排序
def findKthLargest(self, nums, k):
nums.sort()
return nums[len(nums)-k]
堆排序
解决2个问题
- 如何将无序序列构造成初始堆
- 输出堆顶元素后,如何将剩余元素调整成新的堆
# 这个词是由"heap"(堆)和"ify"(使成为)组合而成的
# n:代表待堆化的数组的长度。i:代表当前需要堆化的节点在数组中的索引位置。
# 这个是大根堆,调整堆的方法:
def Heapify(arr, n, i):
largest = i
# 下标为0时的规则
l = 2*i + 1
r = 2*i + 2
# 判断是否越界以及是否需要进行调整
if l < n and arr[l] > arr[largest]:
largest = l
if r < n and arr[r] > arr[largest]:
largest = r
# 如果需要进行调整,进行交换再进行递归处理
if largest != i:
arr[i], arr[largest] = arr[largest], arr[i]
Heapify(arr, n, largest)
def HeapSort(arr):
n = len(arr)
for i in range(n//2 - 1, -1, -1):
Heapify(arr, n, i)
for i in range(n-1, 0, -1):
arr[0], arr[i] = arr[i], arr[0]
Heapify(arr, i, 0)
return arr
DP
动态规划中每一个状态一定是由上一个状态推导出来的
解题步骤
- 确定dp数组(dp table)以及下标的含义
- 确定递推公式
- dp数组如何初始化
- 确定遍历顺序
- 举例推导dp数组
斐波那契数
用DP的思想进行分析
Unique Path
def uniquePaths(self, m, n):
"""
:type m: int
:type n: int
:rtype: int
"""
dp = [[0] * n for _ in range(m)]
# 初始化
for i in range(m):
dp[i][0] = 1
for j in range(n):
dp[0][j] = 1
# 上面这2步能描述清楚么?
for i in range(1, m):
for j in range(1, n):
dp[i][j] = dp[i - 1][j] + dp[i][j - 1]
return dp[-1][-1]
class Solution:
def uniquePaths(self, m: int, n: int) -> int:
# [[1] * n],这种情况不要忘记了,只有这样才能构成一个列表,和后面的列表进行相加
f = [[1] * n] + [[1] + [0] * (n - 1) for _ in range(m - 1)]
print(f)
for i in range(1, m):
for j in range(1, n):
f[i][j] = f[i - 1][j] + f[i][j - 1]
return f[m - 1][n - 1]
滚动数组
f(i,j)f(i, j)f(i,j) 仅与第 iii 行和第 i−1i-1i−1 行的状态有关,因此我们可以使用滚动数组代替代码中的二维数组,使空间复杂度降低为 O(n)
组合数学
二叉树
前序和中序遍历还原二叉树(递归)
二叉树的直径
1:局部变量需要加上self
2:求某个节点的深度,应该为max(L, R) + 1
class Solution:
def diameterOfBinaryTree(self, root: TreeNode) -> int:
self.ans = 1
def depth(node):
# 访问到空节点了,返回0
if not node:
return 0
# 左儿子为根的子树的深度
L = depth(node.left)
# 右儿子为根的子树的深度
R = depth(node.right)
# 计算d_node即L+R+1 并更新ans
self.ans = max(self.ans, L + R + 1)
# 返回该节点为根的子树的深度
return max(L, R) + 1
depth(root)
return self.ans - 1
作者:力扣官方题解
链接:https://leetcode.cn/problems/diameter-of-binary-tree/solutions/139683/er-cha-shu-de-zhi-jing-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
二叉树的最近公共祖先
1、求最小公共祖先,需要从底向上遍历,那么二叉树,只能通过后序遍历(即:回溯)实现从底向上的遍历方式。
2、要理解如果返回值left为空,right不为空为什么要返回right,为什么可以用返回right传给上一层结果
3、为什么情况一就包含了情况二,因为只需要找到最近公共祖先就行
美团2024年0406春招实习笔试
1.最少修改次数
hot100
哈希
Two Sum
字母异位词
mp = collections.defaultdict(list)的用法
the longest consecutive elements sequence.
没有考虑-109 <= nums[i] <= 109的取值范围
标记法行不通
class Solution:
def longestConsecutive(self, nums: List[int]) -> int:
if not nums:
return 0
max_length_nums = max(nums)+1
sign = [0] * max_length_nums
for num in nums:
sign[num] = 1
max_length = 0
res = 0
for i in range(max_length_nums):
if sign[i] == 1:
max_length += 1
res = max(res, max_length)
else:
max_length = 0
return res
哈希表
怎么优化到o(1)的时间复杂度
简单来说就是每个数都判断一次这个数是不是连续序列的开头那个数。
暴力法(超时)
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
res=set()
for i in range(len(nums)-2):
for j in range(i+1,len(nums)-1):
for k in range(j+1,len(nums)):
if nums[i]+nums[j]+nums[k]==0:
cur=tuple(sorted([nums[i],nums[j],nums[k]]))
res.add(cur)
#res.add((sorted([nums[i],nums[j],nums[k]])))
# 把set中的元素转为list
return [list(item) for item in res]
# 出现的错误
# 1:集合中只能加入元组这种不可变的类型,否则报错TypeError: unhashable type: 'list'
# 2:sort和sorted的区别:https://docs.pingcode.com/ask/29934.html
# 3:输出的格式为list中嵌套list,那么需要将set中的内容迭代进行输出
双指针
3sum
list.sort(cmp=None, key=None, reverse=False)
1:nums.sort()中的()没写
2:i=0没有进行初始化
3:i+=1
continue
没有更新i合j、k的值,这样会造成死循环
from typing import List
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
nums.sort()
res = []
i=0
while i<len(nums)-2:
if i>0 and nums[i]==nums[i-1]:
i+=1
continue
j,k=i+1,len(nums)-1
while j<k:
if(nums[j]+nums[k]+nums[i]==0):
if j>i+1 and nums[j]==nums[j-1]:
j+=1
continue
res.append([nums[i],nums[j],nums[k]])
j+=1
k-=1
elif(nums[j]+nums[k]+nums[i]<0):
j+=1
else:
k-=1
i+=1
return res
nums = [-1,0,1,2,-1,-4]
print(Solution().threeSum(nums))
字串
链表
相交链表(双指针,清晰图解)
# 这种写法就非常巧妙
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
A, B = headA, headB
while A != B:
A = A.next if A else headB
B = B.next if B else headA
return A
学会用循环和函数的思想精简代码
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> Optional[ListNode]:
m,n=0,0
cur=headA
while cur:
m+=1
cur=cur.next
cur=headB
while cur:
n+=1
cur=cur.next
cura=headA
curb=headB
if m>n:
step=m-n
while step:
cura=cura.next
step-=1
else:
step=n-m
while step:
curb=curb.next
step-=1
while cura and curb and cura!=curb:
cura=cura.next
curb=curb.next
return cura
链表逆置
二叉树
层序遍历
注意为空和temp的作用
class Solution:
def rightSideView(self, root: Optional[TreeNode]) -> List[int]:
if not root:
return []
res = []
deque = collections.deque()
deque.append(root)
# 用来保存当前层遍历的输出的节点
# deque.append(root)
# temp.append(root)
while deque:
# temp=[]
length = len(deque)
for i in range(length):
cur = deque.popleft()
if i ==length - 1:
res.append(cur.val)
# temp.append(cur.val)
if cur.left:
deque.append(cur.left)
if cur.right:
deque.append(cur.right)
return res
flatten the tree into a “linked list”
只考虑右指针,左指针没有断掉
for i in range(len(self.queue)-1):
self.queue[i].right=self.queue[i+1]
self.queue[i].left = None
return self.queue[0]
从前序与中序遍历序列构造二叉树
用边界坐标构造;记得用哈希表
def myBuildTree(preorder_left: int, preorder_right: int, inorder_left: int, inorder_right: int):
图论
前缀树
【【数据结构 10】Trie|前缀树|字典树】https://www.bilibili.com/video/BV1Az4y1S7c7?vd_source=22a0d494d7d586e6e37e23570688a816
强调文本 强调文本
回溯
全排列
- 忘记用visit数组
- 回溯返回之后忘记修改变量的值
排列
1:想当然的写成了startindex的形式def backtracing(nums, start, path),2:应该用used数组保存
3:res.append(path[:])深浅拷贝的问题4:backtrack ing直接调用没什么问题,但是如果加了self,调用的时候也应该加上
https://www.cnblogs.com/dgp-zjz/p/17480261.html
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
def backtracing(nums, used, path):
if len(path) == len(nums):
#res.append(path[:])
# res.append(path)
return
for i in range(len(nums)):
if used[i]:
continue
used[i] = True
path.append(nums[i])
backtracing(nums, used, path)
path.pop()
used[i] = False
res = []
used = [False] * len(nums)
backtracing(nums, used, [])
return res
栈
字符串解码
class Solution:
def decodeString(self, s: str) -> str:
st=[]
for i in range(len(s)):
if s[i].isdigit():
if i==0 or not s[i-1].isdigit():
st.append(int(s[i]))
else:
st[-1]=st[-1]*10+int(s[i])
elif s[i].islower() or s[i]=='[':
st.append(s[i])
else:
temp = ""
while st[-1]!='[':
temp+=st.pop()
st.pop()
temp=st.pop()*temp[::-1]
# 弄明白把临时字符串加入栈中的区别,栈的意义
for j in range(len(temp)):
st.append(temp[j])
# st.append(temp)
return ''.join(str(i) for i in st)
dp
Minimum Path Sum
1:关于dp数组的构造
dp=[[[0] for _ in range(m)] for _ in range(n)]
最长回文字符串
1:dp = [[False] * n for _ in range(n)]采用循环的方式进行定义,而不是dp = [[False] * n] * n乘法,其会当你使用乘法操作符 * 来复制列表时,你并没有创建新的列表,而是创建了多个指向相同列表对象的引用
2:长度为2时需要单独进行处理
技巧
颜色分类
1:nums[i]=0的代码可以进行复用
2:if f_2<i可以加在while循环中
3:看到有增减的就要判断其合理性
class Solution:
def sortColors(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
n = len(nums)
f_0, f_2 = 0, n - 1
i = 0
while i <= f_2:
if nums[i] == 0:
nums[f_0], nums[i] = nums[i], nums[f_0]
# 考虑交换之后值的情况
f_0 += 1
while nums[i] == 2:
nums[f_2], nums[i] = nums[i], nums[f_2]
# 考虑交换之后值的情况
f_2 -= 1
if f_2<i:
break
if nums[i] == 0:
nums[f_0], nums[i] = nums[i], nums[f_0]
# 考虑交换之后值的情况
f_0 += 1
i += 1
self_attention
参考
https://blog.csdn.net/qq_44949041/article/details/128087174
import torch
import torch.nn as nn
from math import sqrt
class SelfAttention(nn.Module):
def __init__(self,dim_input,dim_q,dim_v):
super(SelfAttention, self).__init__()# 如果不加上这个会报错
self.dim_input = dim_input
self.dim_q = dim_q
self.dim_k = dim_q
self.dim_v = dim_v
self.linear_q = nn.Linear(dim_input,dim_q,bias=False)
self.linear_k = nn.Linear(dim_input,dim_q,bias=False)
self.linear_v = nn.Linear(dim_input,dim_v,bias=False)
self.sqrt=sqrt(self.dim_k)
def forward(self,x):
batch,seq_len,dim_input = x.shape
q=self.linear_q(x)
k=self.linear_k(x)
v=self.linear_v(x)
dist=q.bmm(k.transpose(1,2))/self.sqrt
dist=dist.softmax(dim=-1)
output=dist.bmm(v)
return output
# 示例输入
batch_size = 2
seq_len = 4
dim_input = 8 # 输入数据的维度
dim_q = 8 # 查询矩阵的列维度
dim_v = 8 # 输出数据的列维度
# 随机生成输入数据
x = torch.rand(batch_size, seq_len, dim_input)
# 定义自注意力模型
self_attention = SelfAttention(dim_input, dim_q, dim_v)
# 前向传播
output = self_attention(x)
# 打印输入和输出
print("Input shape:", x.shape)
print("Output shape:", output.shape)
print("Output:", output)