LeetCode上的剑指offer题
刷题ing…
#遇见了得先问面试官时间和空间复杂度的要求
#1.排序+一个下一个
#时间O(nlogn) 空间O(1)
class Solution:
def findRepeatNumber(self, nums: List[int]) -> int:
nums.sort()
for i in range(len(nums)-1):
if nums[i]==nums[i+1]:
return nums[i]
#2.hash
#时间O(n),空间O(n)
#Python中的成员资格(membership)检查运算“in”,在列表(list)中遍历成员,时间复杂度为O(N); 在字典(dict)中, 时间复杂度为O(N)
class Solution:
def findRepeatNumber(self, nums: List[int]) -> int:
memo = dict()
for num in nums:
if not memo.__contains__(num):
memo[num]=1
else:
return num
#3.原地哈希
#时间O(n),空间O(1)
class Solution:
def findRepeatNumber(self, nums: List[int]) -> int:
n = len(nums)
#把原列表当哈希再用
for i in range(n):
while i!= nums[i]:
if nums[i] == nums[nums[i]]:
return nums[i]
temp = nums[i]
nums[i],nums[temp]= nums[temp],nums[i]
#1.视作BST_递归
#从右上角开始比较,比它大就往下数一行,比它小就往左数一列
class Solution:
def findNumberIn2DArray(self, matrix: List[List[int]], target: int) -> bool:
if not matrix:
return False
self.target = target
self.res = False
self.helper(0,len(matrix[0])-1,matrix)
return self.res
def helper(self,i,j,matrix):
if i<len(matrix) and j>=0:
if matrix[i][j]==self.target:
self.res = True
if matrix[i][j]>self.target:
self.helper(i,j-1,matrix)
else:
self.helper(i+1,j,matrix)
#2.视作BST_迭代
class Solution:
def findNumberIn2DArray(self, matrix: List[List[int]], target: int) -> bool:
if not matrix:
return False
n = len(matrix)
m = len(matrix[0])
i = 0
j = m-1
while i<len(matrix) and j>=0:
if matrix[i][j]==target:
return True
elif matrix[i][j]>target:
j-=1
else:
i+=1
return False
#3.暴力_内循环二分查找剪枝
class Solution:
def findNumberIn2DArray(self, matrix: List[List[int]], target: int) -> bool:
#暴力,内循环用二分查找
if not matrix:
return False
n = len(matrix)
m = len(matrix[0])
def helper(line,i,j,target):
if i>j:
return False
mid = (i+j)//2
if line[mid]==target:
return True
elif line[mid]>target:
return helper(line,i,mid-1,target)
else:
return helper(line,mid+1,j,target)
for i in range(n):
if helper(matrix[i],0,m-1,target):
return True
return False
#1.一般py字符串操作
class Solution:
def replaceSpace(self, s: str) -> str:
return s.replace(' ','%20')
#2.一般遍历,外部空间使用
class Solution:
def replaceSpace(self, s: str) -> str:
ans = ''
for l in s:
if l==' ':
ans+='%20'
else:
ans+=l
return ans
#1.常规压栈
class Solution:
def reversePrint(self, head: ListNode) -> List[int]:
#因为数组返回,可以直接压栈
p = head
stack = []
while p:
stack.append(p.val)
p = p.next
return stack[::-1]
#2.递归栈
class Solution:
def reversePrint(self, head: ListNode) -> List[int]:
if not head:
return []
else:
return self.reversePrint(head.next) + [head.val]
#3.无栈,两次遍历
class Solution:
def reversePrint(self, head: ListNode) -> List[int]:
p = head
cnt=0
while p:
cnt+=1
p = p.next
p = head
ans = [0]*cnt
while cnt>0:
ans[cnt-1]=p.val
cnt -= 1
p = p.next
return ans
#1.递归
class Solution:
def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
def helper(preleft,preright,inleft,inright,preorder,inorder):
if preleft>preright:
return
root = TreeNode(preorder[preleft])
p = inleft
while inorder[p]!=root.val:
p+=1
left = p-inleft
root.left = helper(preleft+1,preleft+left,inleft,inleft+left-1,preorder,inorder)
root.right = helper(preleft+left+1,preright,inleft+left+1,inright,preorder,inorder)
return root
return helper(0,len(preorder)-1,0,len(inorder)-1,preorder,inorder)
#2.迭代
'''
看第i个元素位于当前root的left还是right就看中序有无遍历到root,未遍历到就是在root左侧,左子树,反之右子树
使用栈保存遍历过的节点。初始时令中序遍历的指针指向第一个元素,遍历前序遍历的数组,如果前序遍历的元素不等于中序遍历的指针指向的元素,则前序遍历的元素为上一个节点的左子节点。如果前序遍历的元素等于中序遍历的指针指向的元素,则正向遍历中序遍历的元素同时反向遍历前序遍历的元素,找到最后一次相等的元素,将前序遍历的下一个节点作为最后一次相等的元素的右子节点。其中,反向遍历前序遍历的元素可通过栈的弹出元素实现。
'''
class Solution:
def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
if not preorder:
return None
root = TreeNode(preorder[0])
length = len(preorder)
stack = []
stack.append(root)#节点入栈
index = 0
for i in range(1, length):
preorderval = preorder[i]
node = stack[-1]
if node.val != inorder[index]: # 每次比较栈顶元素和inorder[index]
node.left = TreeNode(preorderval)
stack.append(node.left)
else:
while stack and stack[-1].val == inorder[index]:# 栈顶元素等于inorder[index],弹出;并且index += 1
node = stack[-1]
stack.pop()
index += 1
node.right = TreeNode(preorderval)
stack.append(node.right)
return root
#1.脑子一热只写了一个栈
class CQueue:
def __init__(self):
self.deque = []
def appendTail(self, value: int) -> None:
self.deque.append(value)
def deleteHead(self) -> int:
if len(self.deque)==0:
return -1
else:
ans = self.deque[0]
self.deque = self.deque[1:]
return ans
#2.双栈
'''
其中 stack1 用于存储元素,stack2 用于辅助操作
若stack1非空而stack2空,则将1清空入栈2,2用于辅助pop
'''
class CQueue:
def __init__(self):
self.stack1 = []
self.stack2 = []
def appendTail(self, value: int) -> None:
self.stack1.append(value)
def deleteHead(self) -> int:
if self.stack2:#平时就从栈2pop反序的,就是FIFO了
return self.stack2.pop()
elif not self.stack1:#两个栈都空了,没得吐了
return -1
else:#栈2空了,栈1数据倾倒进去
while self.stack1:#栈尾变栈顶,反向堆到stack2里边
self.stack2.append(self.stack1.pop())
return self.stack2.pop()
#1.暴力
class Solution:
def fib(self, n: int) -> int:
a0 = 0
a1 = 1
if n == 0:
return 0
if n==1:
return 1
ans = 0
for i in range(n-1):
ans = a0+a1
a0 = a1
a1 = ans
return ans % 1000000007
#2.暴力记忆化递归
'''
在递归法的基础上,新建一个长度为 nn 的数组,用于在递归时存储 f(0)f(0) 至 f(n)f(n) 的数字值,重复遇到某数字则直接从数组取用,避免了重复的递归计算。
'''
class Solution:
@lru_cache(None)
def fib(self, n: int) -> int:
if n <2:
return n
return (self.fib(n-1)+self.fib(n-2))% 1000000007
#1.dp
class Solution:
def numWays(self, n: int) -> int:
#dpdp????
if n<2:
return 1
dp = [0]*(n+1)
dp[0]=1
dp[1]=1
for i in range(2,n+1):
dp[i] = dp[i-1]+dp[i-2]
return dp[-1]%1000000007
#2.dp空间优化
class Solution:
def numWays(self, n: int) -> int:
#dpdp空间优化
if n<2:
return 1
pre0=1
pre1=1
for i in range(2,n+1):
ans = pre0+pre1
pre0 = pre1
pre1 = ans
return ans%1000000007
#1.一次遍历找转折点
class Solution:
def minArray(self, numbers: List[int]) -> int:
for i in range(len(numbers)-1):
if numbers[i+1]<numbers[i]:
return numbers[i+1]
else:
return numbers[0]
#2.二分_要注意边界条件
class Solution:
def minArray(self, numbers: List[int]) -> int:
l = 0
r = len(numbers)-1
while l<r:
mid = (r-l)//2+l
if numbers[mid]<numbers[r] and numbers[mid]<=numbers[l]:
#右半边有序,转折点确定在左边
r=mid
elif numbers[mid]>=numbers[l] and numbers[mid]>numbers[r]:
#左半有序,确定转折点在右,mid肯定不是
l=mid+1
else:#完全有序数列
r-=1
return numbers[l]
#1.边界条件十分痛苦DFS
class Solution:
def exist(self, board: List[List[str]], word: str) -> bool:
#dfs
def dfs(board,i,j,cnt,word):
m = len(board)
n = len(board[0])
if cnt==len(word):
return True
if i<0 or i>=m or j<0 or j>=n or board[i][j]!=word[cnt]:
return False
board[i][j]='*'
for x,y in [(0,1),(0,-1),(1,0),(-1,0)]:
idx1 = i+x
idx2 = j+y
#print(board[idx1][idx2],word[cnt])
if dfs(board,idx1,idx2,cnt+1,word):
return True
#这个进入点没能找到,返回来的时候cnt可以作为标识
board[i][j] = word[cnt]
return False
m = len(board)
n = len(board[0])
if m<1 and n<1 and not word:
return False
for i in range(m):
for j in range(n):
if dfs(board,i,j,0,word):
return True
return False
#1.dfs
class Solution:
def movingCount(self, m: int, n: int, k: int) -> int:
self.visited = [[False]*n for _ in range(m)]
return self.dfs(0,0,m,n,k)
def dfs(self,i,j,m,n,k):
if (i<0 or i>=m or j<0 or j>=n or (i//10+i%10+j//10+j%10)>k or self.visited[i][j]):
return 0
self.visited[i][j] = True
return self.dfs(i+1,j,m,n,k)+self.dfs(i-1,j,m,n,k)+self.dfs(i,j+1,m,n,k)+self.dfs(i,j-1,m,n,k)+1
#1.dpdp
class Solution:
def cuttingRope(self, n: int) -> int:
#dpdp
dp = [0]*(n+1)
dp[1]=1
for i in range(2,n+1):
for j in range(1,i):
#j是减下来的长度,dp记录当前长度最大切割乘积
#max(dp[j],j)决定用切下来的那段再切切还是就不切了比较长
#和dpi比较看需不需要在长度j这里切割
dp[i] = max(max(dp[j],j)*(i-j),dp[i])
return dp[-1]
#2.奇妙数学法
#推导——尽可能将n长度三等分
'''
利用均值不等式求出乘积最大值 L(m)=(n/m)^m 对此式求导(可利用对数法),可以证明当 m=n/e 时,乘积取最大,此时每段绳子的长度为 n/(n/e)=e,自然对数e的值为2.718,接近3
'''
class Solution:
def cuttingRope(self, n: int) -> int:
if n <= 3: return n - 1
a, b = n // 3, n % 3
if b == 0: return int(math.pow(3, a))
if b == 1: return int(math.pow(3, a - 1) * 4)
return int(math.pow(3, a) * 2)
class Solution:
def cuttingRope(self, n: int) -> int:
#dpdp
dp = [0]*(n+1)
dp[1]=1
for i in range(2,n+1):
for j in range(1,i):
#j是减下来的长度,dp记录当前长度最大切割乘积
#max(dp[j],j)决定用切下来的那段再切切还是就不切了比较长
#和dpi比较看需不需要在长度j这里切割
dp[i] = max(max(dp[j],j)*(i-j),dp[i])
#大数的话在这一步就会溢出
return dp[-1]% 1000000007
#1.位运算1
class Solution:
def hammingWeight(self, n: int) -> int:
cnt=0
while n!=0:
'''
1100:减一后变为1011
1100&1011=1000
n与减1后的数做与运算会减少原本n的1个数
即有几个1就可以做几次与运算
'''
cnt+=1
n&=n-1
return cnt
#2.位运算2
class Solution:
def hammingWeight(self, n: int) -> int:
cnt=0
while n!=0:
'''
有符号右移>>(若正数,高位补0,负数,高位补1)
无符号右移>>>(不论正负,高位均补0)
然而py没有无符号右移
'''
cnt+=n&1
n>>=1
return cnt
#1.迭代快速幂,非位运算考量,其实位运算也可
class Solution:
def myPow(self, x: float, n: int) -> float:
#奇数、偶数、负数
#偶数的话直接翻倍,奇数的话在外边存储一倍
#一般快速幂
if n==0:
return 1
if n==1:
return x
if n==-1:
return 1/x
m = abs(n)
tmp = []
while m>1:
if m%2==0:
x*=x
m=m//2
else:
tmp.append(x)
x*=x
m=m//2
while tmp:
x*=tmp.pop()
if n<0:
return 1/x
return x
#2.递归
class Solution:
def myPow(self, x: float, n: int) -> float:
if n == 0:
return 1
if n < 0:
return 1 / self.myPow(x, -n)
# 如果是奇数
if n & 1:
return x * self.myPow(x, n - 1)
return self.myPow(x * x, n // 2)
#1.普通
class Solution:
def printNumbers(self, n: int) -> List[int]:
return list(range(1,10**n))
#2.字符串,不过还不是大数,毕竟int(''.join(tmp))
class Solution:
def printNumbers(self, n: int) -> List[int]:
res = []
tmp = ['']*n
def helper(idx):
if idx==n:
res.append(int(''.join(tmp)))
return
for i in range(10):
tmp[idx]=chr(ord('0')+i)
helper(idx+1)
helper(0)
return res[1:]
#3.大数-string操作
class Solution:
def printNumbers(self, n: int) -> List[int]:
def helper(cur_s: str, place: int, increas: int): # place = -1, -2, -3
if abs(place) > len(cur_s):
#倒着数位数,用abs
cur_s = '1' + cur_s
#进位
return cur_s
else:
if cur_s[place] != '9':
if place == -1:
cur_s = cur_s[:place] + str(int(cur_s[place]) + 1)
else:
cur_s = cur_s[:place] + str(int(cur_s[place]) + 1) + cur_s[place+1:]
return cur_s
else:
#进位时place所在位位清0
if place == -1:
cur_s = cur_s[:place] + '0'
else:
cur_s = cur_s[:place] + '0' + cur_s[place+1:]
return helper(cur_s, place-1, 1)
res = []
cur_s = '0'
while len(cur_s) <= n:
res.append(int(cur_s))
cur_s = helper(cur_s, -1, 1)#这样即便是刚进位的2位数,也照样从个位数开始增加返回
return res[1:]
#1.非空哑结点和一遍遍历
#边界值非空判断其实也可以,但是双指针优雅一点
class Solution:
def deleteNode(self, head: ListNode, val: int) -> ListNode:
dummy = ListNode(0)
dummy.next = head
p = dummy
while p and p.next:
if p.next.val==val:
p.next = p.next.next
break
p = p.next
return dummy.next
假设主串为 AA,模式串为 BB 从最后一步出发,需要关注最后进来的字符。假设 AA 的长度为 nn ,BB 的长度为 mm ,关注正则表达式 BB 的最后一个字符是谁,它有三种可能,正常字符、*∗ 和 .(点),那针对这三种情况讨论即可,如下:
如果 B 的最后一个字符是正常字符,那就是看 A[n-1]是否等于 B[m−1],相等则看A0…n−2与B0…m−2,不等则是不能匹配,这就是子问题。
如果 B 的最后一个字符是.,它能匹配任意字符,直接看A 0…n−2与B 0…m−2
此时:f[i][j]=f[i−1][j−1]
如果 B 的最后一个字符是它代表 B[m-2]=cB[m−2]=c 可以重复0次或多次,它们是一个整体 c
情况一:A[n-1] 是 0 个 c,B 最后两个字符废了,能否匹配取决于A0…n−1 和B0…m−3是否匹配
情况二:A[n-1]是多个 c 中的最后一个(这种情况必须 A[n-1]=c 或者 c=’.’),所以 A 匹配完往前挪一个,B继续匹配,因为可以匹配多个,继续看A0…n−2和 B_{0…m-1}B0…m−1是否匹配。
1:直接砍掉正则串的后面两个, f[i][j] = f[i][j-2]
2:正则串不动,主串前移一个,f[i][j] = f[i-1][j]
dp边界:
空串和空正则是匹配的,f[0][0] = truef[0][0]=true
空串和非空正则,不能直接定义 true 和 false,必须要计算出来。
非空串和空正则必不匹配,f[1][0]=…=f[n][0]=false
#1.dpdp
class Solution:
def isMatch(self, s: str, p: str) -> bool:
#竟然是dp
lenp = len(p)
lens = len(s)
dp = [[False]*(lenp+1) for _ in range(lens+1)]
dp[0][0]=True
for j in range(1,lenp+1):
if p[j-1]=='*':
dp[0][j]=dp[0][j-1] or dp[0][j-2]#空串和非空正则,不能直接定义 true 和 false,必须要计算出来。
for i in range(1,lens+1):
for j in range(1,lenp+1):
if s[i-1]==p[j-1] or p[j-1]=='.':
dp[i][j]=dp[i-1][j-1]#p和s当前字符匹配,进到下一字符
elif p[j-1]=='*':
if s[i-1]==p[j-2] or p[j-2]=='.':#看再往前一个
#当前字符和前一个字符是匹配的
dp[i][j] = dp[i][j-2] or dp[i][j-1] or dp[i-1][j]#true/false传递
#1)删除前一个字符 dp[i][j-2]
#2)保留前一个字符 dp[i][j-1]
#3)复制前一个字符 dp[i-1][j]
else:
#尾上2个p的字符废了,只能删除字符
dp[i][j] = dp[i][j-2]
return dp[-1][-1]
#2.递归
class Solution:
def isMatch(self, s: str, p: str) -> bool:
def match(s, p, i, j):
if j == len(p): return i == len(s)#全空True,只有正则空False,若两个指针都进行到头True
flag = (i != len(s) and (s[i] == p[j] or p[j] == "."))
if j < len(p) - 1 and p[j+1] == "*":
#flag==T,后者为为情况1or情况2;
return flag and match(s, p, i+1, j) or match(s, p, i, j+2)
else:#当前不是*,f[i][j]=f[i−1][j−1]
return flag and match(s, p, i+1, j+1)
return match(s,p,0,0)
#3.递归清晰版
class Solution:
def isMatch(self, s: str, p: str) -> bool:
if not p: return not s
first_match = bool(s) and p[0] in (".", s[0])
if len(p) >= 2 and p[1] == "*":
if first_match:
return self.isMatch(s[1:], p) or self.isMatch(s, p[2:])
return self.isMatch(s, p[2:])
return first_match and self.isMatch(s[1:], p[1:])
#4.Py正则
class Solution:
def isMatch(self, s: str, p: str) -> bool:
return re.fullmatch(p, s) != None
#1.把输入拆分改造成isnumeric可以判断的程度,看按decimal格式拆分的部分是否都由数字组成
class Solution:
def isNumber(self, s: str) -> bool:
'''
空格只能出现在首尾,出现在中间一定是非法的。
正负号只能出现在两个地方,第一个地方是数字的最前面,表示符号。
第二个位置是e后面,表示指数的正负。如果出现在其他的位置一定也是非法的。
e只能出现一次,并且e之后一定要有数字才是合法的,123e这种也是非法的。
小数点,由于e之后的指数一定是整数,所以小数点最多只能出现一次,并且一定要在e之前。
所以如果之前出现过小数点或者是e,再次出现小数点就是非法的。
'''
s = s.strip() #去掉两端的空白符
if not s :
return False
else:
if s[0] in ['+', '-']:
#去掉正负号
s = s[1:]
if 'e' in s:
temp_list = s.split('e')
if len(temp_list) > 2:
#字符串s中含有多于一个的’e‘,返回False
return False
temp_list[0] = temp_list[0].replace('.', '', 1) #去掉e前面的字符串中的'.',只进行一次,还有就是有多个'.'
if len(temp_list[1]) > 0 and temp_list[1][0] in ['+', '-']:
# 去掉e后面字符串中的'+'或者'-',仅去掉一次,还有就是有多个'+', '-'
temp_list[1] = temp_list[1].replace(temp_list[1][0], '', 1)
if temp_list[0].isnumeric() and temp_list[1].isnumeric():
return True
return False
else: # s中不含'e'
s = s.replace('.', '', 1)
if s.isnumeric():
return True
return False
#2.确定有限自动机DFA
#参考:https://leetcode-cn.com/problems/biao-shi-shu-zhi-de-zi-fu-chuan-lcof/solution/que-ding-you-xian-zi-dong-ji-dfa-by-justyou/
class Solution:
def isNumber(self, s: str) -> bool:
if not s:
return False
根据有限状态的图来建立的状态跳转表格
transTable = [
[1,2,7,-1,-1,0],
[-1,2,7,-1,-1,-1],
[-1,2,3,4,-1,9],
[-1,3,-1,4,-1,9],
[6,5,-1,-1,-1,-1],
[-1,5,-1,-1,-1,9],
[-1,5,-1,-1,-1,-1],
[-1,8,-1,-1,-1,-1],
[-1,8,-1,4,-1,9],
[-1,-1,-1,-1,-1,9]
]
cols = {
"sign":0,
"number":1,
".":2,
"exp":3,
"other":4,
"blank":5
}
def get_col(c):
#做判断
if c.isdigit():return 'number'
elif c in {'+','-'}:return 'sign'
elif c == '.':return '.'
elif c in {'E','e'}:return 'exp'
elif c == ' ':return 'blank'
else:return 'other'
legal_state = {2,3,5,8,9}#结束状态
'''
中途遇到空格转到9,若后边还有别的直接就-1out,
'''
state = 0
for c in s:
state = transTable[state][cols[get_col(c)]]#结合当前的状态和当前的字符来跳转状态
if state == -1:
return False#没能跳转出去
return True if state in legal_state else False
#1.暴力
class Solution:
def exchange(self, nums: List[int]) -> List[int]:
left = []
right = []
for i in range(len(nums)):
if nums[i]%2==0:
right.append(nums[i])
else:
left.append(nums[i])
return left+right
#2.快排双指针+swap
class Solution:
def exchange(self, nums: List[int]) -> List[int]:
#快排的双指针妙用
l = 0
r = len(nums)-1
while l<r:
while l<r and nums[r]%2==0:
r-=1
#直到指针对撞或者找到右边第一个奇数停止
while l<r and nums[l]%2!=0:
l+=1
#直到指针对撞或者找到左边第一个偶数停止
nums[l],nums[r]=nums[r],nums[l]#swap
l+=1
r-=1
return nums
#3.快慢双指针
class Solution:
def exchange(self, nums: List[int]) -> List[int]:
#快排的双指针妙用
low = 0
fast = 0
while fast<len(nums):
if nums[fast]%2!=0:
nums[low],nums[fast]=nums[fast],nums[low]
low+=1
fast+=1
return nums
#1.两次遍历法
class Solution:
def getKthFromEnd(self, head: ListNode, k: int) -> ListNode:
p = head
cnt = 0
while p:
cnt+=1
p = p.next
cnt-=k
p = head
while cnt:
p = p.next
cnt-=1
return p
#2.两个指针法
class Solution:
def getKthFromEnd(self, head: ListNode, k: int) -> ListNode:
pre = head
cur = head
cnt = 1
while cnt<k:
cnt+=1
cur = cur.next
while cur.next:
pre = pre.next
cur = cur.next
return pre
#全文背诵
#1.迭代
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
if not head:
return None
pre = None
while head:
next = head.next
head.next = pre
pre = head
head = next
return pre
#2.递归
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
if not head or not head.next:
return head
node = self.reverseList(head.next)
head.next.next = head #自己和邻居闭环
head.next = None #去向通路阻断
return node
#1.递归
class Solution:
def mergeTwoLists(self, l1, l2):
if l1 is None:
return l2
elif l2 is None:
return l1
elif l1.val < l2.val:
l1.next = self.mergeTwoLists(l1.next, l2)
return l1
else:
l2.next = self.mergeTwoLists(l1, l2.next)
return l2
#2.迭代
class Solution:
def mergeTwoLists(self, l1, l2):
dummy = ListNode(0)
pre = dummy
while l1 and l2:
if l1.val<=l2.val:
pre.next = l1
pre = pre.next
l1 = l1.next
else:
pre.next = l2
pre = pre.next
l2 = l2.next
if l1:
pre.next = l1
if l2:
pre.next = l2
return dummy.next