考点:递归
分析:这是一道明显的递归题目,前序遍历 可以将树划分为 根 左 右 中序遍历将树划分为 左 根 右,因此可以先从前序遍历中找出根节点,然后再利用根节点将中序遍历划分为 左子树和右子树,然后继续传递左子树和右子树在根节点的两侧。
难点:如何能够实现中序遍历简单有效划分。并且前序遍历也要用(得靠前序遍历去划分)
解题:来自题解,不直接利用中序遍历而是转向其index,将每个中序遍历对应的index找到。以便于前序遍历找根节点在中序遍历中的位子。以 left,right来表示一个子树,继续向下传递。
# 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:
def buildTree(root,left,right):
if len(left) == 0 or len(right) == 0:
return
root = TreeNode(preorder[0]) # 建立根节点
### 将左右子树分开
index = inorder.index(preorder[0])
left_inorder = inorder[:index]
right_inorder = inorder[index:]
### 将前序遍历左右分开
left_preorder = []
right_preorder = []
for num in preorder:
if num in left_inorder :
left_preorder.append(num)
for num in preorder:
if num in right_inorder :
right_preorder.append(num)
### 建立左边,建立右边
root.left = buildTree(root,left_preorder,left_inorder)
root.right = buildTree(root,right_preorder,right_inorder)
return root
buildTree(TreeNode(0),preorder,inorder)
############### 利用索引的解决方法 ###########
self.dic,self.pro = {},preorder
for i in range(len(inorder)):
self.dic[inorder[i]] = i ###bulid index
return build(0,0,len(inorder)-1) ### initial
def build(self,root_index,left,right):
if left > right:return # index is exceed
root = TreeNode[self.pro[root_index]]# 根节点 index
index = self.dic[self.pro[root_index]] # 中序遍历 根节点 的 index
root.left = build(root_index+1,left,index-1)
# left,right 都指向中序遍历节点,root指向前序遍历
root.right =build(root_index+index-left+1,index+1,right)
# 根节点是 此 节点+下一层左子树长度
# [1,2,3,4,5] [1] [2,3,4] [5] 5 is the next root
# root_index + (index-1-left+1) + 1 左子树右边减去左边 index(4)-index(2)+1
# 额外的加1 是为了跳出左子树 到左子树长度后的第一个节点
return root
考点:DP
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
sum_ = -float('inf')
for i in range(0,len(nums)): ##### O(N^2) 超出时间限制
for j in range(i,len(nums)):
if sum(nums[i:j+1]) > sum_:
sum_ = sum(nums[i:j+1])
return sum_
这个题不同于以往的DP题目的点在于每进行一次迭代操作,要判断当前的dp是否是最大值。所以比以往的多了一个判断过程作为干扰。
DP问题看的都是i状态下和前一个状态下的关系。那么这道题,dp[i] 应该是前i项之和,那么dp[i-1]就应该是前i-1项之和,因为它求解的是 最大值。那么就要看
dp[i-1]是否大于0,如果大于0,那么加上nums[i]是比原来大的。否则直接截断前面(体现剪枝),从nums[i]从新开始。前面是负数那么肯定不会是增大的只能是减小。分支条件出来了,那么整个方程就容易了。
if len(nums) == 0:return 0
if len(nums) == 1 : return nums[0]
dp = [0 for i in range(2)]
dp[0] = nums[0]
max_ = dp[0]
for i in range(1,len(nums)):
if dp[0] > 0:
dp[1] = dp[0] + nums[i]
else:
dp[1] = nums[i] ##体现截断点
dp[0] = dp[1]
if dp[1] > max_: #### 多了一个判断
max_ = dp[1]
return max_
直接模拟栈的入栈和出栈,然后判断出栈的元素是否和栈的出栈相同,相同继续,不同直接返回fasle。
时间复杂度O(N^2) space is O(N) ??是不是O(N^2)
class Solution:
def validateStackSequences(self, pushed: List[int], popped: List[int]) -> bool:
push = []
###### 用双端队列 代替pushed
while(popped):
a = popped.pop(0) #输出第一个需要pop的元素 ####
##
if a not in push:
while(pushed):
b = pushed.pop(0) ##
if b == a:
break
push.append(b)
if a in push :
b = push.pop(-1)
if a!=b:
return False
else:
continue
return True
分析一下上述代码 主要耗时点在 pushed.pop(0)。利用双端队列尝试是否可以降低时间。->提高了一点点
看了题解之后发现不用这么复杂的写,直接从压入栈开始考虑。利用index 来省略 poped.pop()操作。【这一点我老是想不起来 OVO 】
push = []
index = 0
for i in range(0,len(pushed)):
push.append(pushed[i]) ## 入栈
while push and push[-1] == popped[index]: ##判断栈顶是否是出栈的, if is ture
index += 1 # the next pop
push.pop() # stack is pop(-1)
return not push ## if push's length is 0 , is true
6-4 剑指 Offer 26. 树的子结构 【有问题】
6-5-剑指 Offer 61. 扑克牌中的顺子 【做完了】
6-6-剑指 Offer 49. 丑数
考点:DP ,三指针问题,堆
分析:(1三指针问题)
因为n只含有质因子2,3,5,那么这个数只能被2,3,5整除。因此这个数字一定是2.3.5中某两个或者某一个的公倍数。并且这个数字可以用更小的丑数*(2,3,5)组成。比如18:2*9.
那么要找出第·N个这样数字,那么得从比它小的满足条件的数字开始,让1乘以(2,3,5)然后排列。再将第二数乘以2,3,5,再继续找。那么2,3,5,分别可以看成是三个指针。
A:{1*2,2*2,3*2....}
B:{1*3,2*3,3*3,4*3,.....}
C:{1*5,2*5,3*5,4*5,...}
因此是三个指针,从A1 B1 C1开始,如果这个最小那么指针向后移动一位。可能存在A和B中相同的元素,因此不仅仅是A要移动,B 和C 也要判断一下,做相应移动。【题目类似于有序多链表合并问题】上述分析来自此链接@sunrise。
class Solution:
def nthUglyNumber(self, n: int) -> int:
dp = [1 for i in range(n)] # 存储N个值
a,b,c =0,0,0 # index
for i in range(1,n):# dp[0]=1 固定
dp[i] = min(dp[a]*2,dp[b]*3,dp[c]*5) # 从上一个符合要求中乘以2,3,5找最小的
## 注意是 上一个状态的末端
## 更新a,b,c
if dp[i] == dp[a]*2:a+=1
if dp[i] == dp[b]*3:b+=1
if dp[i] == dp[c]*5:c+=1
return dp[n-1]
(2)DP问题
从上面的分析可知,Xn的值一定是 Xi*(2,3,5)得到的,又因为Xn应该是 Xi*(2,3,5)中的最小的那一个。Xi可能来自集合A,B,C中的某一个。所以有了如下的递推关系:
Xn = min(Xa*2, Xb*3, Xc*5)
那么依旧是设置a,b,c分别指向Xa, Xb, Xc。当最小值来自Xa时,更新a指针指向下一个。此时为了避免最小值可能出现相等情况,同时更新b,c的值。代码同上,理解思路不太相同,这个理解角度是从前面通过递推关系得到的。
(3)堆排序。相同题目:面试题 17.09. 第 k 个数
因为题目要求的是第N个,那么这个前面的所有的满足条件的可以做成一个小根堆。因为规定了N最大不超过1690,所以可以直接建立一个N=1690的小根堆,然后依次每次小根堆弹出最小的,直到符合第N个。
在python中有一个模块 heapq 是堆的模块。基本函数操作可详见链接。
下面代码来自提交记录中。我加了一下注释。
class Ugly:
def __init__(self):
#seen = {1, } # 集合 去重作用
hp = [] # 小根堆的存储
heapq.heappush(hp, 1) # 向堆中添加第一个元素,以list为基础
self.nums = nums = [] # 存储从小到大的每个值
for _ in range(1690): # 创建一个1690个元素的小根堆
cur_ugly = heapq.heappop(hp)
self.nums.append(cur_ugly) # 提取最小值(pop是删除)
for i in [2, 3, 5]: # 添加下一个值
new = cur_ugly * i
if new not in hp: ## 更改了一下 原来是 seen,seen没什么必要添加 浪费空间
## 提取的是最小的,那么*2,,*3,*5也应该在heap中,按照是否出现添加到heap中
#seen.add(new) ##集合中添加新值
heapq.heappush(hp, new) ## 向堆中加入新元素
class Solution:
u = Ugly()
def nthUglyNumber(self, n: int) -> int:
return self.u.nums[n-1]
这样做的确很快,是因为class Ugly已经创建好了,但是堆排序是一个时间复杂度为O(NlogN)的算法,并没有比DP的方法快很多O(N),这个快完全是因为N较小,而且后面每次取值都是O(1)的操作。
6-7-剑指 Offer 34. 二叉树中和为某一值的路径 【做完了,整理在了二叉树中】
DFS+回溯
6-8-剑指 Offer 33. 二叉搜索树的后序遍历序列【做完了,整理在了二叉搜索树中 】
单调栈很厉害的一种思路。
(1) 正反全是 BFS。
正序列二叉树:BFS
反序列构建二叉树:BFS [以层次遍历的方式来构建一棵二叉树]
反序列中依旧利用队列的方式,进行node.left 和 node.right的传递,一般情况下是利用递归进行节点和根、子树之间传递的。
def deserialize(self, data): ## 反序列化将root.left 存储在里面,每次给其赋值
if data == "[]": return ## 层次遍历的方式建立一棵二叉树
vals, i = data[1:-1].split(','), 1
root = TreeNode(int(vals[0])) ## 构造根节点
q = [root]
i = 1
while(q): ## 正序列化和反序列化必须保证完全对应,否则可能报错
node = q.pop(0) # 出队伍,找这个节点的左右节点,按照层次遍历,那么i ,i+1就是其左右节点
if vals[i] != 'null':
node.left = TreeNode(int(vals[i])) # 左节点找到
q.append(node.left) # 将该节点存储,用于下一层的寻找,过程
i+=1
if vals[i] != 'null' :
node.right = TreeNode(int(vals[i]))
q.append(node.right)
i+=1
return root
考点:归并排序【分治思想】 相似题目: 327,493。 【树状数组】
分析:归并排序,就是以最小单位来进行排序,然后在合并在一起。
[3,-1] [1,2] 这是合并前的元素,那么从小到大排序的过程中,可以知道一共可以产生多少逆序数。
i =0,j=0。如果left[i] > right[j].那么left和right都是基本有序的(从小到大的),那么 len(left)-i都是比right[i]大的,那么在归并排序的过程中加入此条统计语句就行。
class Solution:
# 归并排序 过程中计数, 时间复杂度 O(NlogN)
#def merge(self, arr1: List[int], arr2: List[int]) -> List[int]:
def merge(self, nums,low,mid,high) -> List[int]:
#global count
container = [] # 用于存储有序数字
i, j = low, mid+1
while i <= mid and j <= high: ##比较合并过程
if nums[i] <= nums[j]:
container.append(nums[i])
i += 1
else:
self.ans += mid-i+1
## self.ans 全局变量
container.append(nums[j])
j += 1
if i <= mid:
container.extend(nums[i:mid+1])
elif j <= high:
container.extend(nums[j:high+1])
nums[low:high+1] = container
#print(arr,low,mid,high)
def merge_sort(self,nums,low,high):
if low<high:
mid = (low+high)//2
self.merge_sort(nums,low,mid)
#print('left,',low,mid)
self.merge_sort(nums,mid+1,high)
#print('right,',mid+1,high)
self.merge(nums,low,mid,high)
#print('merge')
#else:
#return
def reversePairs(self, nums: List[int]) -> int:
self.ans = 0
self.merge_sort(nums,0,len(nums)-1)
return self.ans
分析:查找操作时间复杂度O(1);添加操作:因为插入前序列是有序的,所以可以直接采用插入排序的方式。
利用二分查找法 找插入的位置 O(logN);插入操作时间复杂度O(N).
class MedianFinder:
def __init__(self):
self.sort = []
#self.n = 0
def addNum(self, num: int) -> None:
#self.sort.append(num)
#self.sort.sort()
n = len(self.sort)
if self.n == 0:
self.sort.append(num)
return
## 二分插入排序
i,j=0,n-1
while(i<=j): ## 二分查找
mid = (i+j)//2
if self.sort[mid] > num:
j = mid-1
else:
i = mid+1
self.sort.insert(i,num) #O(N)
def findMedian(self) -> float: # O(1)
n = len(self.sort)
if n == 0:return
if n%2 == 0:
return (sort[n//2]+self.sort[n//2-1])/2
else:
return self.sort[n//2]