目录
注意
题源是在LeetCode平台上《剑指offer》。
使用的是Python3
面试题03. 数组中重复的数字
思路:
- 在一个长度为 n 的数组 nums 里的所有数字都在【0~n-1】 的范围内。
- 请找出数组中任意一个重复的数字。
- 因为每个数字都在【0~n-1】中,利用哈希table就可以找到第一个重复的数字进行返回
class Solution:
def findRepeatNumber(self, nums: List[int]) -> int:
dic_num = {}
for num in nums:
dic_num[num] = dic_num.get(num,0) + 1
if dic_num[num]>1:
return num
面试题04. 二维数组中的查找
思路:
- 从左至右递增↑,从上至下递增↑。
- 则矩阵右上角是这一行最大的这一列最小的。同理左下角也是这一列最大的,这一行最小的。
- 任取一角进行遍历
- 注意n、m可取0,所以要考虑matrix为空的情况
class Solution:
def findNumberIn2DArray(self, matrix: List[List[int]], target: int) -> bool:
if len(matrix) == 0:
return False
rows, cols = len(matrix), len(matrix[0])
i,j = 0, cols-1
while 0 <= i < rows and 0 <= j < cols:
if matrix[i][j] > target:
j -= 1
elif matrix[i][j] < target:
i += 1
elif matrix[i][j] == target:
return True
return False
面试题05. 替换空格
思路:
- s的长度可能为0,要做判断
- 可以使用split函数,以空格为界分成list,再用join以“%20”为间隔结合
- 也可以直接使用replace函数
class Solution:
def replaceSpace(self, s: str) -> str:
if len(s) == 0:
return ""
s = s.split(" ")
return '%20'.join(s)
面试题06. 从尾到头打印链表
思路:
- 链表可能为空,需要处理
- 因为要求返回数组,可以直接用个list装遍历的结果,最后返回从尾到头的结果
- 利用栈先进后出的性质,也可以用栈装入,最后输出到结果list,一样也是从尾到头的结果
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def reversePrint(self, head: ListNode) -> List[int]:
stack = []
if not head:
return []
p = head
while p:
stack.append(p.val)
p = p.next
return stack[::-1]
面试题07. 重建二叉树
思路:
- 前序+中序构建二叉树
- 节点个数可能为0,要处理
- 建树的递归终止条件就是这个区间没有数了,即inl>inr
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
if len(preorder) == 0 or len(inorder) == 0:
return None
return self.build_pre_in(0, 0, len(inorder)-1, preorder, inorder)
def build_pre_in(self, prel, inl, inr, preorder, inorder):
if inl>inr:
return
# 取根节点
p = preorder[prel]
# 找在inorder里的下标
index = inorder.index(p)
root = TreeNode(p)
root.left = self.build_pre_in(prel+1, inl, index-1, preorder, inorder)
root.right = self.build_pre_in(prel+(index-inl)+1, index+1, inr, preorder, inorder)
return root
面试题09. 用两个栈实现队列
思路:
- 考虑好弹出时对空栈的判断即可
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:
return self.stack2.pop()
else:
while self.stack1:
self.stack2.append(self.stack1.pop())
if self.stack2:
return self.stack2.pop()
else:
return -1
# Your CQueue object will be instantiated and called as such:
# obj = CQueue()
# obj.appendTail(value)
# param_2 = obj.deleteHead()
面试题10- I. 斐波那契数列
思路:
- 递归结构很明显,边界也给了,注意判断就可以。
- 注意结果取模
- 递归会有很多重复计算,会导致递归栈爆炸,就可以利用备忘录memo优化
class Solution:
def fib(self, n: int) -> int:
if n < 2:
return n
memo = [0] * (n+1)
memo[0] = 0
memo[1] = 1
for i in range(2, n+1):
memo[i] = memo[i-1]+memo[i-2]
return memo[n]%1000000007
面试题10- II. 青蛙跳台阶问题
思路:
- 斐波那契数列的变形
- 注意结果取模
- 设dp[i]:青蛙跳上i级台阶有dp[i]种跳法
- 跳上i级台阶青蛙可以从i-1级台阶跳上,也可以从i-2级台阶跳上;
- 将问题化为求dp[i]的子问题,求dp[i]时,我已知dp[i-1]和dp[i-2],则dp[i]=dp[i-1]+dp[i-2]
- 形式其实又和斐波那契数列很像了,确定边界,dp[0]=1,dp[1]=1
- 注意:python没有溢出判断,如果不注意取模,你调试可能也不会报溢出错误。
class Solution:
def numWays(self, n: int) -> int:
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[n]%1000000007
面试题11. 旋转数组的最小数字
思路:
- 可以使用二分法
- 将整个待寻找数组[0....len-1]看做两部分[0....a]+[a+1....len-1],其中两部分都是递增数列,我们要寻找的数即下标a+1
- 初始设置left=0,right=len-1,代表我们要寻找的区间[0...len-1],利用二分法每次寻找到mid。当mid<right时,说明mid之后的数都比mid大,那整个数组的最小值应该在[left,mid]中寻找。当mid>right时,说明目标应该在[mid+1,right]中寻找。当mid==right时,将right-1,因为目标绝对在[left,right-1]中。
class Solution:
def minArray(self, numbers: List[int]) -> int:
left,right = 0, len(numbers)-1
while left < right:
mid = (left+right)//2
if numbers[mid] < numbers[right]:
right = mid
elif numbers[mid] > numbers[right]:
left = mid + 1
elif numbers[mid] == numbers[right]:
right -= 1
return numbers[left]
面试题12. 矩阵中的路径
思路:
- 用dfs四个方向分别走
- 路径走到头了,就可以返回true,否则中途遇到越界、不相等、或者已经访问过了就返回false
- 起点不是(0,0),是你路径的起点,所以应该用for循环去寻找起点
- 我本来用个for循环去走的,发现会超时。
class Solution:
def exist(self, board: List[List[str]], word: str) -> bool:
rows,cols = len(board), len(board[0])
visited = [[0]*cols for _ in range(rows)]
def dfs(x, y, k):
if x<0 or x>=rows or y<0 or y>=cols or board[x][y]!=word[k] or visited[x][y]!=0:
return False
if k == len(word)-1:
return True
visited[x][y] = 1
res = dfs(x+1,y,k+1) or dfs(x-1,y,k+1) or dfs(x,y+1,k+1) or dfs(x,y-1,k+1)
visited[x][y] = 0
return res
for i in range(rows):
for j in range(cols):
if dfs(i, j, 0):return True
return False
面试题13. 机器人的运动范围
思路:
- 和上一道题类似,也是利用dfs
- 注意添加一个判断是否超过k
class Solution:
def movingCount(self, m: int, n: int, k: int) -> int:
visited = [[0]*n for _ in range(m)]
return self.dfs(0,0,m,n,k,visited)
def valid_k(self, x, y, k):
res = 0
while x:
res += x%10
x //= 10
while y:
res += y%10
y //= 10
if res > k:
return False
return True
def dfs(self, x, y, m, n, k, visited):
moves = [(1,0),(-1,0),(0,1),(0,-1)]
visited[x][y] = 1
count = 1
for move in moves:
newx = x + move[0]
newy = y + move[1]
if newx<0 or newx>=m:
continue
if newy<0 or newy>=n:
continue
if visited[newx][newy]==1 or not self.valid_k(newx, newy, k):
continue
count += self.dfs(newx, newy, m,n,k,visited)
return count
面试题14- I. 剪绳子
思路:
- 设dp[i]:为长度为i的绳子的最大乘积
- dp[i] = max(dp[i],max(j*(i-j), j*dp[i-j]) ,对于一个长度为i的绳子,我可以剪可以不剪,不剪就是dp[i],剪的话可以从j=(1...i-1)的里任一位置剪,剪了之后剩下的一段i-j又可以继续剪或者不剪,最后保留那个最大的乘积即可。
- 找到边界n=2,dp[2]=1
class Solution:
def cuttingRope(self, n: int) -> int:
dp = [0] * (n+1)
dp[0] = 0
dp[1] = 1
dp[2] = 1
for i in range(3, n+1):
for j in range(1, i):
dp[i] = max(dp[i], max(j*(i-j), j*dp[i-j]))
return dp[n]
面试题14- II. 剪绳子 II
思路:
- 就是比上一题多了一个取模,思路一致
class Solution:
def cuttingRope(self, n: int) -> int:
dp = [0] * (n+1)
dp[2] = 1
for i in range(3, n+1):
for j in range(1, i):
dp[i] = max(dp[i], max(j*(i-j), j*dp[i-j]))
return dp[n]%1000000007
面试题15. 二进制中1的个数
思路:
- 每一位和1相与相加即可得到1的个数
class Solution:
def hammingWeight(self, n: int) -> int:
res = 0
while n:
res += n&1
n = n>>1
return res
面试题16. 数值的整数次方
思路:
- 我一开始就是用循环一次次的乘,但最后超出了时间限制
- 看了题解,这个写的很好(https://leetcode-cn.com/problems/shu-zhi-de-zheng-shu-ci-fang-lcof/solution/mian-shi-ti-16-shu-zhi-de-zheng-shu-ci-fang-kuai-s/)
- 利用二分快速幂
class Solution:
def myPow(self, x: float, n: int) -> float:
if x == 0:
return 0
if n < 0:
x, n = 1/x, -n
res = 1
while n:
if n&1:res *= x
x *= x
n >>= 1
return res
面试题17. 打印从1到最大的n位数
思路:
- 利用for循环,n位数,即[1,10的n次方)
class Solution:
def printNumbers(self, n: int) -> List[int]:
num = 10**n
res = []
for i in range(1, num):
res.append(i)
return res
面试题18. 删除链表的节点
思路:
- 链表题若是要对链表进行操作,可以考虑虚拟一个dummy头节点,使整个链表上的位置可以进行同样处理
- 此题设置pre和p指针,一个指向当前节点的前一个节点,一个指向当前节点
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def deleteNode(self, head: ListNode, val: int) -> ListNode:
dummy = ListNode(-1)
dummy.next = head
pre,p = dummy, head
while p:
if p.val == val:
pre.next = p.next
p = p.next
else:
pre,p = p, p.next
return dummy.next
面试题19. 正则表达式匹配
(先埋个坑在这)
面试题20. 表示数值的字符串
思路:
- 对于字符串题,考虑其可能有多余空格的可能,或者为空的可能。
- 判断字符串是否是合法数值,分情况讨论
- 如果当前位置index是正负号,index=0或者index-1=e/E为合法,其余都不合法
- 如果当前位置index是e/E,则之前(0,index-1)中含有数字没有出现过e/E为合法,其余都不合法
- 如果当前位置index是.,则之前(0,index-1)中没有出现过.没有出现过e/E为合法,其余都不合法(注意.1合法)
- 如果当前位置index是数字,则直接查看下一位
- 最后返回是否出现过数字即可
class Solution:
def isNumber(self, s: str) -> bool:
s = s.strip()
if s == "":
return False
met_dot = met_e = met_digit = False
for i,ch in enumerate(s):
if ch in ['+','-']:
if i != 0 and (s[i-1] not in ['e','E']):
return False
elif ch == '.':
if met_dot or met_e:
return False
met_dot = True
elif ch in ['e', 'E']:
if not met_digit or met_e:
return False
met_e, met_digit = True,False
elif ch.isdigit():
met_digit = True
else:
return False
return met_digit
面试题21. 调整数组顺序使奇数位于偶数前面
思路:
- 对于数组问题,一般都考双指针、二分法、滑动窗口
- 若是双指针,在移动过程中一定要考虑好边界问题i<j,每一次移动过后,或者要进行交换操作都要判断
- 另外对于判断奇偶数的方法可以采用num&1==1\num&1==0
class Solution:
def exchange(self, nums: List[int]) -> List[int]:
if len(nums) == 1:
return nums
i,j = 0, len(nums) - 1
while i<j:
while i<j and (nums[i]&1 == 1):
i += 1
while i<j and (nums[j]&1 == 0):
j -= 1
if i< j:
nums[i], nums[j] = nums[j], nums[i]
return nums
面试题22. 链表中倒数第k个节点
思路:
- 建立一个虚节点,使操作计数更方便
- 若head为空,或者k超过链表长度都应该返回None
- 让fast指针先走k步,再用p节点从头节点和fast同步往后遍历,当fast到达最后一个节点时,p所指的节点即为答案
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def getKthFromEnd(self, head: ListNode, k: int) -> ListNode:
if not head:
return None
dummy = ListNode(-1)
dummy.next = head
fast = dummy
while k and fast:
fast = fast.next
k -= 1
if k != 0:
return None
p = head
while fast.next:
fast = fast.next
p = p.next
return p
面试题24. 反转链表
思路:
- 翻转链表我比较喜欢用头插法
- 记得判断一下链表为空的情况
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
if not head:
return None
dummy = ListNode(-1)
p = head
while p:
q = p.next # 防止断链
p.next = dummy.next
dummy.next = p
p = q
return dummy.next
面试题25. 合并两个排序的链表
思路:
- 建立一个新链表,同时用两个指针分别指向l1,l2,依次遍历,较小的放到新链表上
- 当某一个链表已经遍历完,剩下的数都应该直接接到链表之后
- 记得返回的是新建立的链表的头结点
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
C = ListNode(-1)
p1, p2, p3 = l1, l2, C
while p1 and p2:
if p1.val < p2.val:
p3.next = p1
p3, p1 = p3.next, p1.next
else:
p3.next = p2
p3, p2 = p3.next, p2.next
if p1:
p3.next = p1
p1 = p1.next
if p2:
p3.next, p2 = p2, p2.next
return C.next
面试题26. 树的子结构
思路:
- 树的题一般可以考虑递归,因为树天生有递归的结构
- 当子树递归到了空节点,说明别的节点已经检查完毕,返回true即可
- 若主树为空,但子树不空,或者子树和主树对应节点数值不一样,返回false
- 整个函数从当前节点递归,若没有,则去左子树找,若没有,则去右子树找
- 这期间要保证主和子都不为空,否则返回false
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def isSubStructure(self, A: TreeNode, B: TreeNode) -> bool:
def recur(A, B):
if not B:
return True
if not A or A.val != B.val:
return False
return recur(A.left, B.left) and recur(A.right, B.right)
return recur(A,B) or self.isSubStructure(A.left,B) or self.isSubStructure(A.right, B) if A and B else False
面试题27. 二叉树的镜像
思路:
- 由于这题是要在原本的树结构上更改,所以要注意处理
- 利用先序遍历,若当前根节点的左右节点有一个不为空则就应该将它们位置互换
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def mirrorTree(self, root: TreeNode) -> TreeNode:
self.preorder(root)
return root
def preorder(self, root):
if not root:
return
if root.left or root.right:
root.left,root.right = root.right,root.left
self.preorder(root.left)
self.preorder(root.right)
面试题28. 对称的二叉树
思路:
- 和26题思路有点相近,递归判断当前两个节点是否值相同,或者是否同为空
- 再继续递归检查
- 注意若树为空也返回True
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def isSymmetric(self, root: TreeNode) -> bool:
def recur(A, B):
if not A and not B:
return True
if not A or not B:
return False
if A.val!=B.val:
return False
return recur(A.left, B.right) and recur(A.right, B.left)
return recur(root.left, root.right) if root else True
面试题29. 顺时针打印矩阵
思路:
- 这道题想清楚逻辑会好做一点,设定一个上下左右的边界
- 每一次一行或一列遍历完,则那个边界就往里缩一点,每一次的遍历保持在边界里就可以
class Solution:
def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
if len(matrix)==0 or len(matrix[0])==0:
return []
# 上下左右边界
l,r,u,d = 0, len(matrix[0])-1, 0, len(matrix)-1
res = []
while True:
for i in range(l,r+1):
res.append(matrix[u][i])
u += 1
if u > d:
break
for i in range(u,d+1):
res.append(matrix[i][r])
r -= 1
if l > r:
break
for i in range(r, l-1, -1):
res.append(matrix[d][i])
d -= 1
if u > d:
break
for i in range(d, u-1, -1):
res.append(matrix[i][l])
l += 1
if l > r:
break
return res
面试题30. 包含min函数的栈
思路:
- 这里面的难点主要是返回栈里的min最小数
- 维护一个存储最小数的栈,保证栈顶是最小的数,比栈顶小的插入到最小数栈。
class MinStack:
def __init__(self):
self.stack = []
self.minstack = []
def push(self, x: int) -> None:
self.stack.append(x)
if not self.minstack or x <= self.minstack[-1]:
self.minstack.append(x)
def pop(self) -> None:
if self.stack.pop() == self.minstack[-1]:
self.minstack.pop()
def top(self) -> int:
return self.stack[-1]
def min(self) -> int:
return self.minstack[-1]
面试题31. 栈的压入、弹出序列
思路:
- 判断弹出序列是否合法,就直接模拟一个入栈出栈顺序即可
- 若栈顶等于弹出序列的第一个数,则弹出
- 最后检查栈里是否还有元素
class Solution:
def validateStackSequences(self, pushed: List[int], popped: List[int]) -> bool:
if not pushed or not popped:
return True
stack = []
index = 0
for i in range(len(pushed)):
stack.append(pushed[i])
while index<len(popped) and stack and popped[index]==stack[-1]:
stack.pop()
index += 1
if stack:
return False
return True
面试题32 - I. 从上到下打印二叉树
思路:
- 此题就是单纯的层序遍历
- 利用队列即可,注意判断空节点
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def levelOrder(self, root: TreeNode) -> List[int]:
if not root:
return []
res = []
nextNode = [root]
while nextNode:
x = nextNode.pop(0)
res.append(x.val)
if x.left:
nextNode.append(x.left)
if x.right:
nextNode.append(x.right)
return res
面试题32 - II. 从上到下打印二叉树 II
思路:
- 在上一题的基础上,需要把属于同一层的输出至一个list里
- 利用curres,curnode,nextnode,储存当前一层的节点,和下一层的节点
- 注意空节点
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def levelOrder(self, root: TreeNode) -> List[List[int]]:
if not root:
return []
res = []
curres = []
curNode = [root]
nextNode = []
while True:
while curNode:
x = curNode.pop(0)
curres.append(x.val)
if x.left:
nextNode.append(x.left)
if x.right:
nextNode.append(x.right)
res.append(curres)
if not nextNode:
break
curNode = nextNode
curres = []
nextNode = []
return res
面试题32 - III. 从上到下打印二叉树 III
思路:
- 这道题再在上一题的基础上增加一个判断当前层的curres是否要逆序
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def levelOrder(self, root: TreeNode) -> List[List[int]]:
if not root:
return []
res = []
curres = []
curNode = [root]
nextNode = []
isReverse = False
while True:
while curNode:
x = curNode.pop(0)
curres.append(x.val)
if x.left:
nextNode.append(x.left)
if x.right:
nextNode.append(x.right)
if isReverse:
curres = curres[::-1]
isReverse = not isReverse
res.append(curres)
if not nextNode:
break
curNode = nextNode
nextNode = []
curres = []
return res
面试题33. 二叉搜索树的后序遍历序列
思路:
- 每当题目提到二叉搜索树,无非想两件事,一个是二叉搜索树的左节点<根,右节点>根,一个是中序遍历是递增序列
- 该题给了一个后序遍历序列,让判断是否是二叉搜索树的,那我们就考虑上面的两个条件
- 显然判断左右根的大小更容易
class Solution:
def verifyPostorder(self, postorder: List[int]) -> bool:
if not postorder:
return True
def recur(i, j):
if i >= j:return True
l = i
while postorder[l] < postorder[j]:l += 1
m = l
while postorder[l] > postorder[j]: l += 1
return l == j and recur(i, m-1) and recur(m, j-1)
return recur(0, len(postorder)-1)
面试题34. 二叉树中和为某一值的路径
思路:
- 利用dfs+回溯的方法遍历二叉树
- 每当当前路径走到头,且其和为目标值,则把当前path加入res里,同时记得要弹出当前加入的值,方便其进入下一个不同的路径
- 注意函数里的path,要用path[:],不然传的是空值
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def pathSum(self, root: TreeNode, sum: int) -> List[List[int]]:
if not root:
return []
res = []
def dfs(root, path, cursum):
if not root:
return
cursum += root.val
path.append(root.val)
if not root.left and not root.right and cursum==sum:
res.append(path[:])
path.pop()
return
dfs(root.left, path, cursum)
dfs(root.right, path, cursum)
path.pop()
dfs(root, [], 0)
return res
面试题35. 复杂链表的复制
思路:
- 这道题主要考的是深拷贝的知识点,浅拷贝和深拷贝的差别在:浅拷贝是复制指向节点的指针,本质上还是共享同一块内存位置,深拷贝是开辟了另外一块空间。
- 可以用Python的copy.deepcopy()函数
"""
# Definition for a Node.
class Node:
def __init__(self, x: int, next: 'Node' = None, random: 'Node' = None):
self.val = int(x)
self.next = next
self.random = random
"""
class Solution:
def copyRandomList(self, head: 'Node') -> 'Node':
if not head:
return None
visited = {}
def dfs(head):
if not head:
return None
if head in visited:
return visited[head]
copy = Node(head.val)
visited[head] = copy
copy.next = dfs(head.next)
copy.random = dfs(head.random)
return copy
return dfs(head)
面试题36. 二叉搜索树与双向链表
思路:
- 需要将二叉搜索树改造成为一个双向链表,且是循环双向链表,表尾节点的right指向表头节点,表头结点的left指向表尾节点
- 这个链表肯定需要利用二叉搜索树的性质,中序遍历为递增数组
- 同时我们需要记录表头结点
- 利用全局变量self.pre,self.head记录当前节点的上一个节点,表头结点,当中序遍历完成,self.pre刚好指向表尾节点
- 最后处理下使链表形成循环链表即可
"""
# Definition for a Node.
class Node:
def __init__(self, val, left=None, right=None):
self.val = val
self.left = left
self.right = right
"""
class Solution:
def __init__(self):
self.pre = None
self.head = None
def treeToDoublyList(self, root: 'Node') -> 'Node':
if not root:
return None
def inorder(root):
if not root:
return
inorder(root.left)
if not self.pre:
self.head = root
else:
root.left,self.pre.right = self.pre,root
self.pre = root
inorder(root.right)
inorder(root)
self.pre.right,self.head.left = self.head,self.pre
return self.head
面试题37. 序列化二叉树
思路:
- 题目要求我们将二叉树进行前序遍历序列化,再根据前序遍历的顺序进行反序列化
- 难点主要在如何表示空节点上,只要处理了这个空节点就很好做了,利用‘$’代表空节点即可,反序列化的时候遇到这个符号,就返回None
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Codec:
def serialize(self, root):
"""Encodes a tree to a single string.
:type root: TreeNode
:rtype: str
"""
if not root:
return []
res = []
def preorder(root):
if not root:
res.append('$')
else:
res.append(root.val)
preorder(root.left)
preorder(root.right)
preorder(root)
return res
def deserialize(self, data):
"""Decodes your encoded data to tree.
:type data: str
:rtype: TreeNode
"""
if not data:
return None
x = data.pop(0)
if x=='$':
return None
else:
root = TreeNode(int(x))
root.left = self.deserialize(data)
root.right = self.deserialize(data)
return root
# Your Codec object will be instantiated and called as such:
# codec = Codec()
# codec.deserialize(codec.serialize(root))
面试题38. 字符串的排列
思路:
- 我的思路是用dfs+回溯,每条路都走一遍,用set存结果,防止重复
class Solution:
def permutation(self, s: str) -> List[str]:
if not s:
return []
n = len(s)
visited = [0]*len(s)
res = set()
def dfs(cur, path):
if cur >= n:
return
path.append(s[cur])
visited[cur] = 1
if len(path) == n:
res.add(''.join(path))
dic = set()
for i in range(n):
if visited[i] == 0:
dfs(i, path)
path.pop()
visited[cur] = 0
for i in range(len(s)):
dfs(i, [])
return list(res)
面试题39. 数组中出现次数超过一半的数字
思路:
- 此题采用哈希表,当当前数字的出现次数超过数组长度一半,则直接返回该数字
class Solution:
def majorityElement(self, nums: List[int]) -> int:
dic = {}
n = len(nums)
for num in nums:
dic[num] = dic.get(num,0) + 1
if dic[num] > n//2:
return num
面试题40. 最小的k个数
思路:
- 采用快排,每一轮快排,会固定一个位置,当固定的位置正好是k-1,则直接输出arr[:k]即可
return self.quick_sort(arr, k)
def quick_sort(self, arr, k):
left = 0
right = len(arr)-1
while left<right:
p = self.partition(left, right, arr)
if p == k-1:
break
elif p > k-1:
right = p-1
elif p < k-1:
left = p+1
return arr[:k]
def partition(self, i, j, arr):
while True:
while arr[i]<arr[j]:
i += 1
else:
arr[i],arr[j] = arr[j],arr[i]
if i>=j:
break
j -= 1
while arr[i]<arr[j]:
j -= 1
else:
arr[i],arr[j] = arr[j],arr[i]
if i>=j:
break
i += 1
return i
面试题41. 数据流中的中位数
思路:
- 这道题单纯的在需要计算中位数时进行排序也可以,但那不是最优的方法
- 最优的方法是维持两个优先队列(大顶堆,小顶堆),比中位数小的全部在大顶堆中,且堆顶是小于中位数的最大数。比中位数大的全部在小顶堆中,且堆顶是大于中位数的最小数,或者就是中位数本身。
- 由于python中没有大顶堆,只能用乘以一个负数表示
class MedianFinder:
def __init__(self):
"""
initialize your data structure here.
"""
self.min_heap = []
self.max_heap = []
def addNum(self, num: int) -> None:
if len(self.min_heap) == len(self.max_heap):
heapq.heappush(self.min_heap, -heapq.heappushpop(self.max_heap, -num))
else:
heapq.heappush(self.max_heap, -heapq.heappushpop(self.min_heap, num))
def findMedian(self) -> float:
if len(self.min_heap) == len(self.max_heap):
return (-self.max_heap[0] + self.min_heap[0])/2
else:
return self.min_heap[0]
# Your MedianFinder object will be instantiated and called as such:
# obj = MedianFinder()
# obj.addNum(num)
# param_2 = obj.findMedian()
面试题42. 连续子数组的最大和
思路:
- 若当前连续子数组和小于0,则后面不管加什么都不会是最大和。则将其清0
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
res = 0
max_res = nums[0]
for num in nums:
res += num
max_res = max(max_res, res)
if res<0:
res = 0
return max_res
面试题43. 1~n整数中1出现的次数
思路:
- 这题只能靠总结规律来做,我每次都会忘记。参考了这个题解(https://leetcode-cn.com/problems/1nzheng-shu-zhong-1chu-xian-de-ci-shu-lcof/solution/wo-de-jian-dan-jie-fa-by-ai-bian-cheng-de-zhou-n-2/)
class Solution:
def countDigitOne(self, n: int) -> int:
res = 0
i = 1
while n // i:
high = n//(i*10)
cur = (n//i)%10
low = n - (n//i)*i
if cur == 0:
res += high*i
elif cur == 1:
res += high*i + low + 1
else:
res += (high+1)*i
i *= 10
return res
面试题44. 数字序列中某一位的数字
思路:
- 1-9:占9x1下标;10-99:占90x2个下标;100-999:占900x3个下标....以此类推
- 根据给的n依次减掉下标占的数,得到目标是几位数(注意一位下标还有个0要处理)
- 此时的n是digits位下标中的第n个,对digits整除得到是digits位中的第几个数,取余得到target中的第几位
class Solution:
def findNthDigit(self, n: int) -> int:
base = 9
digits = 1
if n > 9:
n -= 1
else:
return n
while n - digits*base > 0:
n -= digits*base
base *= 10
digits += 1
# print(digits)
idx = n % digits
number = 1
for i in range(1, digits):
number *= 10
target = number + n//digits
res = str(target)
# print(res)
return int(res[idx])
面试题45. 把数组排成最小的数
思路:
- 通过写比较函数__lt__
class CMP(str):
def __lt__(self, y):
return self+y<y+self
class Solution:
def minNumber(self, nums: List[int]) -> str:
res = sorted(map(str,nums), key=CMP)
# print(res)
return ''.join(res)
面试题46. 把数字翻译成字符串
思路:
- 这道题用动态规划,找到状态方程,dp[i]代表第i位共有几种组合方式
- 转移方程dp[i]=dp[i-1]+dp[i-2](i位和i-1位组合在一起也可以表示一个字母),dp[i]=dp[i-1](i位和i-1位组合在一起不能表示一个字母)
- 初始,dp[0]=dp[1]=1
class Solution:
def translateNum(self, num: int) -> int:
s = str(num)
n = len(s)
dp = [0]*(n+1)
dp[0] = dp[1] = 1
if n < 2:
return dp[n]
for i in range(2, n+1):
if s[i-2]=='1' or (s[i-2]=='2' and s[i-1]<'6'):
dp[i] = dp[i-1]+dp[i-2]
else:
dp[i] = dp[i-1]
return dp[n]
面试题47. 礼物的最大价值
思路:
- 这道题我一开始使用dfs像遍历完所有路径找到最大的但是会超时
- 这道题可以使用动态规划,dp[i][j]代表当前(i,j)所能拿到的最大礼物价值
- dp[i][j]=max(dp[i-1][j],dp[i][j-1])+grid[i][j],因为只能往下或者往右走,则往会看左边或者上面哪个的值更大就从那边走然后加上当前位置。
- dp[0][0....cols-1],dp[0...rows-1][0]得到边界的初始值
class Solution:
def maxValue(self, grid: List[List[int]]) -> int:
rows,cols= len(grid),len(grid[0])
dp = [[0]*cols for _ in range(rows)]
dp[0][0] = grid[0][0]
for i in range(1,cols):
dp[0][i] = dp[0][i-1] + grid[0][i]
for i in range(1,rows):
dp[i][0] = dp[i-1][0] + grid[i][0]
for i in range(1, rows):
for j in range(1, cols):
dp[i][j] = max(dp[i][j-1], dp[i-1][j]) + grid[i][j]
return dp[rows-1][cols-1]
面试题48. 最长不含重复字符的子字符串
思路:
- 利用滑动窗口,i记录目标字符串的头,j记录目标字符串的尾
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
if len(s)==0:
return 0
i = 0
max_len = 1
for j in range(1, len(s)):
while s[j] in s[i:j]:
i += 1
else:
max_len = max(max_len, j-i+1)
return max_len
面试题49. 丑数
思路:
- 丑数只能由2,3,5为因子组成,可以设置三个指针p2,p3,p5分别代表当前因子所能乘到的地方
- 维护一个dp列表,i表示第i个丑数,每轮用三个指针所指位置乘以自己的因子,得到的结果取最小,即为当前位置的丑数
class Solution:
def nthUglyNumber(self, n: int) -> int:
p2,p3,p5 = 1,1,1
dp = [0]*(n+1)
dp[1] = 1
for i in range(2, n+1):
dp[i] = min(dp[p2]*2, dp[p3]*3, dp[p5]*5)
if dp[i] == dp[p2]*2:
p2 += 1
if dp[i] == dp[p3]*3:
p3 += 1
if dp[i] == dp[p5]*5:
p5 += 1
return dp[n]
面试题50. 第一个只出现一次的字符
思路:
- 用哈希做,注意处理字符串为空和没有答案的情况
class Solution:
def firstUniqChar(self, s: str) -> str:
if not s:
return " "
dic = {}
for ch in s:
dic[ch] = dic.get(ch,0) + 1
for k,v in dic.items():
if v == 1:
return k
return " "
面试题51. 数组中的逆序对
(埋个坑)
面试题52. 两个链表的第一个公共节点
思路:
- 寻找两个链表的公共节点,利用双指针法(https://leetcode-cn.com/problems/liang-ge-lian-biao-de-di-yi-ge-gong-gong-jie-dian-lcof/solution/shuang-zhi-zhen-fa-lang-man-xiang-yu-by-ml-zimingm/)可以参考这篇有图画出来更方便理解
# 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) -> ListNode:
if not headA or not headB:
return None
pa, pb = headA, headB
while pa != pb:
pa = pa.next if pa else headB
pb = pb.next if pb else headA
return pa
面试题53 - I. 在排序数组中查找数字 I
思路:
- 注意这个题又是排序树组,第一反应可以考虑二分法
- 这里用二分法寻找左右边界即可
class Solution:
def search(self, nums: List[int], target: int) -> int:
if target not in nums:
return 0
n = len(nums)
i,j = 0, n-1
# 找左边界
while i <= j:
mid = (i+j)//2
if nums[mid] < target:
i = mid+1
elif nums[mid] >= target:
j = mid-1
l = j
i, j = 0, n-1
# 找右边界
while i <= j:
mid = (i+j)//2
if nums[mid] <= target:
i = mid+1
elif nums[mid] > target:
j = mid-1
r = i
# print(str(l)+" "+str(r))
return r-l-1
面试题53 - II. 0~n-1中缺失的数字
思路:
- 同样有序数组考虑二分法
- 若nums[mid]=mid说明缺失的数字在[mid+1,r]中
- 若nums[mid]!=mid说明缺失的数字在[l,mid-1]中
class Solution:
def missingNumber(self, nums: List[int]) -> int:
l,r = 0, len(nums)-1
while l <= r:
mid = (l+r)//2
if nums[mid] == mid:
l = mid+1
else:
r = mid-1
return l
面试题54. 二叉搜索树的第k大节点
思路:
- 利用二叉搜索树的性质,中序遍历是个递增序列,即可知道第k大的节点
- 也可以考虑右根左这样的遍历顺序,就可以得到递减序列。
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def kthLargest(self, root: TreeNode, k: int) -> int:
res = []
self.inorder(root, res)
return res[-k]
def inorder(self, root, res):
if not root:
return
self.inorder(root.left, res)
res.append(root.val)
self.inorder(root.right, res)
面试题55 - I. 二叉树的深度
思路:
- 这道题主要求解二叉树的深度,可以用dfs深度遍历,同时记录当前depth,用个全局变量max_depth来记录最大深度
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def maxDepth(self, root: TreeNode) -> int:
self.max_depth = 0
if not root:
return self.max_depth
def recur(root, depth):
if not root:
return
self.max_depth = max(depth, self.max_depth)
recur(root.left, depth+1)
recur(root.right, depth+1)
recur(root, 1)
return self.max_depth
面试题55 - II. 平衡二叉树
思路:
- 平衡二叉树可以运用递归来做
- 写一个确定当前节点的高度的函数
- 然后递归地判断当前节点的左右子节点的高度差,不满足平衡条件返回false
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def isBalanced(self, root: TreeNode) -> bool:
if not root:
return True
if abs(self.getHeight(root.left)-self.getHeight(root.right))>1:
return False
else:
return self.isBalanced(root.left) and self.isBalanced(root.right)
def getHeight(self, root):
if not root:
return 0
return max(self.getHeight(root.left), self.getHeight(root.right))+1
面试题56 - I. 数组中数字出现的次数
思路:
-
相同的数异或为0,不同的异或为1。0和任何数异或等于这个数本身。
-
所以,数组里面所有数异或 = 目标两个数异或 。 由于这两个数不同,所以异或结果必然不为0。
-
假设数组异或的二进制结果为10010,那么说明这两个数从右向左数第2位是不同的
-
那么可以根据数组里面所有数的第二位为0或者1将数组划分为2个。这样做可以将目标数必然分散在不同的数组中,而且相同的数必然落在同一个数组中。
-
这两个数组里面的数各自进行异或,得到的结果就是答案
class Solution:
def singleNumbers(self, nums: List[int]) -> List[int]:
ret, index = 0, 0
for n in nums:
ret ^= n
while ret & 1 == 0:
index += 1
ret >>= 1
r1, r2 = 0, 0
for n in nums:
if (n >> index) & 1 == 0:
r1 ^= n
else:
r2 ^= n
return [r1, r2]
面试题56 - II. 数组中数字出现的次数 II
思路:
- 当遇到目标数字只是一个的时候,可以直接用数学的方式解决
class Solution:
def singleNumber(self, nums: List[int]) -> int:
return (sum(set(nums))*3 - sum(nums))//2
面试题57. 和为s的两个数字
思路:
- 又是一个有序的列表,考虑双指针i,j分别指向列表的头尾
- 如果i,j指针相加的值等于target,则返回结果,若大于说明j的值过大,应该减小,若小于说明i的值过小,应该增大
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
i,j = 0, len(nums)-1
while i<j:
if nums[i] + nums[j] > target:
j -= 1
elif nums[i] + nums[j] < target:
i += 1
elif nums[i] + nums[j] == target:
return [nums[i], nums[j]]
return []
面试题57 - II. 和为s的连续正数序列
思路:
- 输出答案的序列依然是从小到大,考虑两个指针。
- target最大可由target//2+1组成,之后的连续子序列之和必然大于target
class Solution:
def findContinuousSequence(self, target: int) -> List[List[int]]:
n = target//2 + 1
i, j = 1, 2
res = []
while j<=n:
tmp = [x for x in range(i, j+1)]
if sum(tmp)<target:
j += 1
elif sum(tmp)>target:
i += 1
elif sum(tmp)==target:
res.append(tmp)
j += 1
return res
面试题58 - I. 翻转单词顺序
思路:
- 利用strip函数去掉头尾的空格
- 利用split函数根据空格将单词分隔开
- 若列表里有空的,则略过
class Solution:
def reverseWords(self, s: str) -> str:
s = s.strip()
if not s:
return ""
lis = s.split(" ")
res = []
for i in range(len(lis)):
if lis[i] != '':
res.append(lis[i])
return ' '.join(res[::-1])
面试题58 - II. 左旋转字符串
思路:
- 常用方法,(a,b)->(a^-1,b^-1)->(b,a)
- 其中^-1代表取反的意思,可以自己写个reverse函数,也可以直接用python[::-1]实现(验证后面的方法更省时)
class Solution:
def reverseLeftWords(self, s: str, n: int) -> str:
if len(s) == 1:
return s
sa = s[:n]
sb = s[n:]
s = sa[::-1]+sb[::-1]
return s[::-1]
# def reverse(self, s):
# s = list(s)
# length = len(s)
# i,j=0,length-1
# while i<j:
# s[i], s[j] = s[j], s[i]
# i += 1
# j -= 1
# return ''.join(s)
面试题59 - I. 滑动窗口的最大值
思路:
- 针对这道题,我们可以维护一个优先队列,始终保持队头是当前窗口里最大的值的下标
- 之所以存储的是下标,是因为若队头的下标已经不在滑动窗口里的话,是需要弹出的
class Solution:
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
res = []
deque = []
for i,num in enumerate(nums):
while deque and num > nums[deque[-1]]:
deque.pop()
deque.append(i)
if i<k-1:
continue
while i-k>=deque[0]:
deque.pop(0)
index = deque[0]
res.append(nums[index])
return res
面试题59 - II. 队列的最大值
思路:
- 这一题和上面一题类似,维持一个队列
class MaxQueue:
def __init__(self):
self.dequeue = []
self.queue = []
def max_value(self) -> int:
return self.dequeue[0] if self.dequeue else -1
def push_back(self, value: int) -> None:
self.queue.append(value)
while self.dequeue and value>self.dequeue[-1]:
self.dequeue.pop()
self.dequeue.append(value)
def pop_front(self) -> int:
res = self.queue[0] if self.queue else -1
if self.queue:
self.queue.pop(0)
if res == self.dequeue[0]:
self.dequeue.pop(0)
return res
# Your MaxQueue object will be instantiated and called as such:
# obj = MaxQueue()
# param_1 = obj.max_value()
# obj.push_back(value)
# param_3 = obj.pop_front()
面试题60. n个骰子的点数
思路:
- 这是一道模拟题,计算n个骰子掷出来的结果值出现的概率
- n个骰子掷出来的结果肯定在(n~6*n)
- 所有可能的结果为6^n,所以只需计算n个骰子掷出来的结果值出现的次数分别除以6^n即可
- 利用dp[i][j]来表示i个骰子投出j出现的次数
- 状态转移方程是dp[i][j]=dp[i][j]+dp[i-1][j-k],当j可以由j-k和k得到(其中j-k肯定不能小于i)
- 边界,dp[1][1....6]=1
class Solution:
def twoSum(self, n: int) -> List[float]:
dp = [[0]*(6*n+1) for _ in range(n+1)]
for i in range(1, 7):dp[1][i] = 1
for i in range(2, n+1):
for j in range(i, i*6+1):
for k in range(1, 7):
if j-k < i-1:break
dp[i][j] += dp[i-1][j-k]
return [x/6**n for x in dp[n][n:6*n+1]]
面试题61. 扑克牌中的顺子
思路:
- 五张牌的顺子,最大值和最小值的差肯定是小于等于5的
- 遇到0直接continue
- 同时遇到重复数字肯定不是顺子,返回false
class Solution:
def isStraight(self, nums: List[int]) -> bool:
dic={
'A':1,
'J':11,
'Q':12,
'K':13
}
min_,max_ = 14, -1
for index,num in enumerate(nums):
if num == 0:
continue
if num in nums[:index]:
return False
if num in ['A','J','Q','K']:
num = dic[num]
min_ = min(min_, num)
max_ = max(max_, num)
if max_-min_+1>5:
return False
return True
面试题62. 圆圈中最后剩下的数字
思路:
- 每轮删除的下标为(start+m-1+len)%len
- 找到下标后pop出来,同时更新start
- 直到圆圈中只有一个数了,就返回结果
class Solution:
def lastRemaining(self, n: int, m: int) -> int:
circle = [x for x in range(n)]
start = 0
while len(circle)!=1:
index = (start+m-1+len(circle))%len(circle)
circle.pop(index)
start = index
return circle[0]
面试题63. 股票的最大利润
思路:
- 经典的股票买卖问题,具体操作可以看https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/solution/yi-ge-fang-fa-tuan-mie-6-dao-gu-piao-wen-ti-by-l-3/
class Solution:
def maxProfit(self, prices: List[int]) -> int:
dp = [[0,0] for _ in range(len(prices)+1)]
dp[0][0]=0
dp[0][1]=-float('INF')
for i in range(1, len(prices)+1):
dp[i][0] = max(dp[i-1][0], dp[i-1][1]+prices[i-1])
dp[i][1] = max(dp[i-1][1], 0-prices[i-1])
return dp[len(prices)][0]
面试题64. 求1+2+…+n
思路:
- 利用递归相加,若当前数字为0,则返回0即可
- [更新]题目要求不能使用if、for等关键词。则for->递归,if->逻辑与即可判断
class Solution:
def sumNums(self, n: int) -> int:
return n and (n + self.sumNums(n-1))
面试题65. 不用加减乘除做加法
思路:
- 题目要求不能用加减乘除做加法,那整个思路只能往位运算想
- n=a⊕b(非进位和:异或运算)
- c=a&b<<1(进位:与运算+左移一位)
class Solution:
def add(self, a: int, b: int) -> int:
while b:
num = (a^b)&0xFFFFFFFF
carry = ((a&b)<<1)&0xFFFFFFFF
a = num
b = carry
return a if a <= 0x7FFFFFFF else ~(a^0xFFFFFFFF)
面试题66. 构建乘积数组
思路:
- 将每一项的乘子写出来,可以划分成两个数组
- A_L即A0...Ai-1的部分,A_R即Ai+1...An-1的部分
class Solution:
def constructArr(self, a: List[int]) -> List[int]:
n = len(a)
if n==0:
return []
A_L = [0 for _ in range(n)]
A_R = [0 for _ in range(n)]
A_L[0] = 1
A_R[n-1] = 1
for i in range(1, n):
A_L[i] = A_L[i-1]*a[i-1]
for i in range(n-2, -1, -1):
A_R[i] = A_R[i+1]*a[i+1]
B = [0 for _ in range(n)]
for i in range(n):
B[i] = A_L[i]*A_R[i]
return B
面试题67. 把字符串转换成整数
思路:
- 处理好几种情况即可
- 首先是开头含有空格删除掉,第一个非空字符不是数字或者正负号返回0 ,只有正负号也返回0 ,只获取第一节有效数据
- 若是按超过了[-2^31,2^31-1]只输出最值即可,因为python中没有溢出,需要我们自己判断
class Solution:
def strToInt(self, str: str) -> int:
# 去除空格
s = str.strip()
if not s:
return 0
j = 0
for i in range(len(s)):
x = s[i]
# print(x)
if i == 0 and not ('0'<=x<='9') and x not in ['+','-']:
return 0
if x in ['+','-']:
j = i+1
else:
j = i
while j < len(s) and ('0'<=s[j]<='9'):
j += 1
else:
if len(s[i:j]) and (s[i:j] in ['+', '-']):
return 0
break
num = int(s[i:j])
INT_MIN = -2**31
INT_MAX = 2**31-1
if num < INT_MIN:
return INT_MIN
elif num > INT_MAX:
return INT_MAX
else:
return num
面试题68 - I. 二叉搜索树的最近公共祖先
思路:
- 二叉搜索树的特点,左节点小于根节点,右节点大于根节点
- 从根结点出发,若当前节点比pq都大,说明最近公共祖先应该在左子树中。若当前节点比pq都小,说明最近公共祖先应该在右子树中。
- 最后直接返回答案即可
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
while root:
if root.val>p.val and root.val>q.val:
root = root.left
elif root.val<p.val and root.val<q.val:
root = root.right
else:
return root
面试题68 - II. 二叉树的最近公共祖先
思路:
- 没有了上一题的二叉搜索树的限定,那就利用二叉树最本质的特点,递归
- 递归的在左子树、右子树中寻找最近公共祖先,若当前为空则返回,若当前节点就是寻找节点也直接返回。
- 若这个祖先不是根节点,则其必定在左子树或者右子树中
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def lowestCommonAncestor(self, root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode:
if not root or root==p or root==q:
return root
L = self.lowestCommonAncestor(root.left, p, q)
R = self.lowestCommonAncestor(root.right, p, q)
if not L:
return R
if not R:
return L
return root