文章目录
- 快速排序
- 归并排序
- 冒泡排序
- 二分查找
- 二分查找的拓展——上下界与个数问题
- 最小生成树
- 线段树和树状数组的异同
- 两个栈模拟队列和双端队列
- 最长上升子序列,dp实现,nlogn实现
- 快排时间复杂度和空间复杂度
- 布隆过滤器知道吗?用在什么场景下?推导会么(加分项)
- K个链表归并
- 二叉树前序中序遍历,重建二叉树
- 二叉树的前序遍历和中序遍历的非递归实现
- 哈希表相关
- 大数相加,大数相乘
- 判断完全二叉树、满二叉树、二叉搜索树BST
- 链表反转
- 反转二叉树(镜像二叉树)
- 稳定和非稳定的排序算法有哪些
- 二分查找递归和非递归的时间和空间复杂度
- 输入补全可以用哪个数据结构来做
- 编辑距离
- 和等于 k 的最短子数组长度
- 判断是否为对称二叉树
- 旋转数组:不用额外内存
- 二叉树的序列化与反序列化
- 第k大的数,两种做法 (quick select, heap)
- 判断链表是否有环以及环的位置,证明
- 青蛙跳台阶+有一次后退机会
- 最长公共子串、最长公共子序列
- 二叉树路径最大和
- 蓄水池采样问题
- 2 sum,3 sum
- 一个排序数组能够构成多少个二叉搜索树
- 分解质因数
- 接雨水
- 旋转数组的最小数字
- 最小的K个数
快速排序
参考:快速排序partition过程常见的两种写法+快速排序非递归实现
import random
def partition(arr, low, high):
r=random.randint(low,high) # 随机选[low,high]内的数与arr[low]交换
arr[r],arr[low]=arr[low],arr[r]
pivot=arr[low]
while low < high:
while low < high and arr[high] >= pivot:
high-=1
arr[low] = arr[high]#从后面开始找到第一个小于pivot的元素,放到low位置
while low < high and arr[low] <= pivot:
low+=1
arr[high] = arr[low]#从前面开始找到第一个大于pivot的元素,放到high位置
arr[low] = pivot#最后枢纽元放到low的位置
return low
def quick_sort(arr, low, high):
if low < high:
mid = partition(arr, low, high)
quick_sort(arr, low, mid-1)
quick_sort(arr, mid+1, high)
归并排序
def merge(left, right):
"""合并两个已排序好的列表,产生一个新的已排序好的列表"""
result = [] # 新的已排序好的列表
i = 0 # 下标
j = 0
while i < len(left) and j < len(right):
if left[i] <= right[j]:
result.append(left[i])
i += 1
else:
result.append(right[j])
j += 1
result += left[i:]
result += right[j:]
return result
def merge_sort(seq):
if len(seq) <= 1:
return seq
mid = len(seq) // 2 # 将列表分成更小的两个列表
# 分别对左右两个列表进行处理,分别返回两个排序好的列表
left = merge_sort(seq[:mid])
right = merge_sort(seq[mid:])
# 对排序好的两个列表合并,产生一个新的排序好的列表
return merge(left, right)
冒泡排序
def bubble_sort(arr):
n = len(arr)
# 进行n次冒泡,每次排好一个
for i in range(n):
# 最后 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]
二分查找
def binary_search(array,t):
low = 0
high = len(array)-1
while low <= high:
mid = (low+high)//2
if array[mid] < t:
low = mid + 1
elif array[mid] > t:
high = mid - 1
else:
return mid
return -1
二分查找的拓展——上下界与个数问题
1、二分查找求上下界
2、二分查找求上界和下界
注意点:
1、(low+high)/2
容易溢出
2、注意边界条件即各个while
中的不等号处理
leetcode题目链接:LC34. Find First and Last Position of Element in Sorted Array
另外两个关于二分查找的题目:
153. Find Minimum in Rotated Sorted Array
33. Search in Rotated Sorted Array
'''
利用二分查找求出插入位置,在该位置插入后可以保持序列仍然有序
bs_lower_bound返回的是可插入的最小位置
bs_upper_bound返回的是可插入的最大位置
换句话说,当元素存在且有多个时,返回值还有另外的含义:
bs_lower_bound返回其第一次出现的位置
bs_upper_bound返回其最后一次出现的位置 + 1
'''
def bs_lower_bound(arr, t):
low = 0
high = len(arr) # 注意high初始化
while low < high: # 注意没有等号
mid = (high - low) // 2 + low
if arr[mid] >= t:
high = mid # 求下界则把等号放到上边界对应的分支
else:
low = mid + 1
return low
# 如果要在找不到该数字时返回-1而不是它可以插入的最低位置,则使用下述返回代码
return low if 0<=low<len(nums) and nums[low]==tar else -1
def bs_upper_bound(arr, t):
low = 0
high = len(arr) # 注意high初始化
while low < high: # 注意没有等号
mid = (high - low) // 2 + low
if arr[mid] > t:
high = mid
else:
low = mid + 1 # 求上界则把等号放到下边界对应的分支
return low
# 如果要在找不到该数字时返回-1而不是它可以插入的最高位置,则使用下述返回代码
return low if 0<low<=len(nums) and nums[low-1]==tar else -1
def searchRange(nums, target):
"""
在nums中查找target的最左出现位置和最右出现位置,如果不存在则返回[-1,-1]
"""
if len(nums) == 0:
return [-1, -1]
low = bs_lower_bound(nums, target)
# bs_high找到的位置是可插入的最小位置,就等于元素出现的最左位置;如果等于len(nums)说明不存在
if low>=len(nums) or nums[low] != target:
low = -1
# bs_high找到的位置是可插入的最大位置,减一才是元素出现的最右位置;如果等于0说明不存在
high = bs_upper_bound(nums, target) - 1 #注意减一
if high<0 or nums[high] != target:
high = -1
return [low, high]
nums = [1, 1, 3, 3, 3, 5, 5, 5]
print("nums: ", nums)
print("index:", list(range(len(nums))))
print('= ' * 10)
print('lower bound of 0: ', bs_lower_bound(nums, 0))
print('lower bound of 1: ', bs_lower_bound(nums, 1))
print('lower bound of 2: ', bs_lower_bound(nums, 2))
print('lower bound of 3: ', bs_lower_bound(nums, 3))
print('lower bound of 4: ', bs_lower_bound(nums, 4))
print('lower bound of 5: ', bs_lower_bound(nums, 5))
print('lower bound of 6: ', bs_lower_bound(nums, 6))
print('= ' * 10)
print('upper bound of 0: ', bs_upper_bound(nums, 0))
print('upper bound of 1: ', bs_upper_bound(nums, 1))
print('upper bound of 2: ', bs_upper_bound(nums, 2))
print('upper bound of 3: ', bs_upper_bound(nums, 3))
print('upper bound of 4: ', bs_upper_bound(nums, 4))
print('upper bound of 5: ', bs_upper_bound(nums, 5))
print('upper bound of 6: ', bs_upper_bound(nums, 6))
'''
输出:
nums: [1, 1, 3, 3, 3, 5, 5, 5]
index: [0, 1, 2, 3, 4, 5, 6, 7]
= = = = = = = = = =
lower bound of 0: 0
lower bound of 1: 0
lower bound of 2: 2
lower bound of 3: 2
lower bound of 4: 5
lower bound of 5: 5
lower bound of 6: 8
= = = = = = = = = =
upper bound of 0: 0
upper bound of 1: 2
upper bound of 2: 2
upper bound of 3: 5
upper bound of 4: 5
upper bound of 5: 8
upper bound of 6: 8
'''
最小生成树
TODO
线段树和树状数组的异同
TODO
两个栈模拟队列和双端队列
栈 s1, s2 实现队列
(后)入队操作:
将元素压入s1
(前)出队操作:
if s2不为空:
弹出栈顶元素
elif s1不为空:
将s1的元素逐个倒入s2,但最后一个元素弹出后直接返回而不用压入s2
else:
抛出队列为空的异常
双端队列:
栈 s1, s2 实现双端队列
后入队操作:
将元素压入s1
前入队操作:
将元素压入s2
前出队操作:
if s2不为空:
弹出栈顶元素
elif s1不为空:
将s1的元素逐个倒入s2,但最后一个元素弹出后直接返回而不用压入s2
else:
抛出队列为空的异常
后出队操作:
if s1不为空:
弹出栈顶元素
elif s2不为空:
将s2的元素逐个倒入s1,但最后一个元素弹出后直接返回而不用压入s1
else:
抛出队列为空的异常
最长上升子序列,dp实现,nlogn实现
dp实现见资料[1]
nlogn实现,解释见[2],python代码实现如下:
class Solution:
def lengthOfLIS(self, nums):
if len(nums)==0:
return 0
tails=[nums[0]] # 第一个数直接加入
for i in range(1,len(nums)): # 遍历一遍其余数
x=nums[i]
if x > tails[-1]: # 如果大于tails中的所有数则直接加入(注意tails是升序的)
tails.append(x)
else: # 否则二分查找出下界,然后*替换*掉该位置的元素
low,high=0,len(tails)
while low<high:
mid=low+(high-low)//2
if tails[mid]>=x:
high=mid
else:
low=mid+1
tails[low]=x # 注意找到下界后是替换而不是insert
return len(tails)
快排时间复杂度和空间复杂度
最好情况是选中的pivot为中值,因此划分均衡
最坏情况是选中的pivot为最小值或最大值,导致每次划分为1个和 剩余
若选取pivot的方法是直接用low或high位置的元素,则最坏情况=数组有序,避免方法是随机选取pivot
时间复杂度:
平均情况下是 ,最坏情况下(数组有序)是
空间复杂度:
快排的实现是递归调用的, 而且每次函数调用中只使用了常数的空间,因此空间复杂度等于递归深度,在和平均情况下为 ,为
布隆过滤器知道吗?用在什么场景下?推导会么(加分项)
K个链表归并
题目:https://leetcode.com/problems/merge-k-sorted-lists/submissions/
参考:https://leetcode.com/problems/merge-k-sorted-lists/discuss/10511/10-line-python-solution-with-priority-queue
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
import queue
class Solution(object):
def mergeKLists(self, lists):
pq=queue.PriorityQueue()
# 需要定义__lt__函数用于定义何为“更小”,这里使用结点的值来定义小顶堆
# 将True和False调换即可定义为结点值的大顶堆
ListNode.__lt__=lambda x,y: True if x.val<y.val else False
for node in lists:
if node is not None:
pq.put(node)
dummy=ListNode(None)
curr=dummy
while pq.qsize()>0:
curr.next=pq.get()
curr=curr.next
if curr.next is not None:
pq.put(curr.next)
return dummy.next
二叉树前序中序遍历,重建二叉树
# 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 inorder:
ind = inorder.index(preorder.pop(0))
root = TreeNode(inorder[ind])
root.left = self.buildTree(preorder, inorder[0:ind])
root.right = self.buildTree(preorder, inorder[ind+1:])
return root
一般的实现是将preorder划分两部分给左右子树的构建,但是这里用pop()弹出了结点,而且是先对左子树构建,所以到了右子树的时候,preorder中左子树的所有结点刚好pop完了
注意:若给前序和中序,直接问后序遍历的结果,可以先建树,然后再后序遍历。
二叉树的前序遍历和中序遍历的非递归实现
题目地址:
https://leetcode.com/problems/binary-tree-inorder-traversal/
https://leetcode.com/problems/binary-tree-preorder-traversal/
参考:https://blog.csdn.net/Applying/article/details/84982712
前序遍历
# 二叉树结点定义见前面的《二叉树前序中序遍历,重建二叉树 》
class Solution:
def preorderTraversal(self, root: TreeNode) -> List[int]:
res = []
if root is None:
return res
stack = []
stack.append(root)
while len(stack)!=0:
p = stack.pop()
# 访问,即加入结果list
res.append(p.val)
# 这里注意,要先压入右子节点,再压入左节点
if p.right is not None:
stack.append(p.right)
if p.left is not None:
stack.append(p.left)
return res
# 二叉树结点定义见前面的《二叉树前序中序遍历,重建二叉树 》
class Solution:
def inorderTraversal(self, root: TreeNode) -> List[int]:
res=[]
if root is None:
return res
stack = []
p = root
# 栈非空 或 p非空
while len(stack)!=0 or p is not None:
# 向左搜索,寻找最左的节点,即中序遍历的第一个节点
while p is not None:
stack.append(p)
p = p.left
# 如果栈非空则访问当前节点并往右走一步表示接下来访问其右子树
if len(stack)!=0:
r = stack.pop()
res.append(r.val)
p = r.right
return res
哈希表相关
解决哈希冲突:
- 开放定址法
- 线性探测法
- 平方探测法
- 链地址法
常用哈希函数:课本常介绍除留余数法,Fasttext用的是FNV-1a,文件校验常用MD5、SHA-1
大数相加,大数相乘
TODO
判断完全二叉树、满二叉树、二叉搜索树BST
判断二叉树是否为完全二叉树
以层次遍历的方法, 找到第一个两个儿子不都存在的节点
if 此节点没有左子树且有右子树:
不是完全二叉树
else 看下一个节点:
如果下一个节点是叶子,则是完全二叉树,否则不是完全二叉树
判断是否为满二叉树
# 返回是否为满二叉树以及该树的最大深度
def is is_full(p):
if p is None:
return True, 0
else:
lres, lhei = is_full(p.left)
rres, rhei = is_full(p.right)
if lres and rres and lhei==rhei: # 左子树和右子树都是满二叉树且高度相等
return True, lhei+1
else:
return False, max(lhei, rhei)+1
判断是否为BST
中序遍历的结果若有序则是BST,否则不是
链表反转
头插法
反转二叉树(镜像二叉树)
交换树中所有节点的左右子节点,即得到树的镜像。
def invert(root):
if root is None:
return root
tmp = root.left
root.left = invert(root.right)
root.right= invert(tmp)
return root
稳定和非稳定的排序算法有哪些
稳定算法包括:归并、冒泡、插入、基数。“归泡插基”
二分查找递归和非递归的时间和空间复杂度
非递归:时间 ,空间
递归:时间 ,空间
输入补全可以用哪个数据结构来做
字典树,见:如何实现搜索框的关键词提示功能
编辑距离
和等于 k 的最短子数组长度
要求:如果用动态规划要优化时间,用贪心法需要证明
Leetcode有道类似的:
判断是否为对称二叉树
def sym(p, q):
if p is None and q is None:
return True
elif p is not None and q is not None:
# 对称的条件:p和q的值相同,并且p的左子树与q的右子树对称,p的右子树与q的左子树对称
return (p.val == q.val) and sym(p.left,q.right) and sym(p.right,q.left)
return False
class Solution:
def isSymmetric(self, root: TreeNode) -> bool:
return root is None or sym(root.left,root.right)
旋转数组:不用额外内存
def reverse(nums,low,high):
while low < high:
nums[low], nums[high] = nums[high], nums[low]
low+=1
high-=1
class Solution:
# Do not return anything, modify nums in-place instead.
def rotate(self, nums: List[int], k: int) -> None:
k %= len(nums)
reverse(nums, 0, len(nums)-1)
reverse(nums, 0, k - 1)
reverse(nums, k, len(nums) - 1)
二叉树的序列化与反序列化
# -*- coding:utf-8 -*-
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
# 我把原文中list改为deque,因为list的pop(0)复杂度高,可以用deque的popleft代替
from collections import deque
class Solution:
def Serialize(self, root):
if not root:
return '#'
return str(root.val) +',' + self.Serialize(root.left) +','+ self.Serialize(root.right)
def Deserialize(self, s):
nodes = deque(s.split(','))
return self.deserializeTree(nodes)
def deserializeTree(self, nodes):
if len(nodes)<=0:
return None
val = nodes.popleft()
root = None
if val != '#':
root = TreeNode(int(val))
root.left = self.deserializeTree(nodes)
root.right = self.deserializeTree(nodes)
return root
第k大的数,两种做法 (quick select, heap)
在从小到大排序后的数组中,第大的数是从开始算的第小的数,下标从 开始,因此第大的数下标为 .
方法一:quick select
class Solution {
public:
int partition(vector<int>& arr,int low,int high){
int r = (rand() % (high-low+1))+ low; // [low, high]
swap(arr[r], arr[low);
int pivot = arr[low];
while(low<high){
while(low<high && arr[high]>=pivot)high--;
arr[low]=arr[high];
while(low<high && arr[low]<=pivot)low++;
arr[high]=arr[low];
}
arr[low]=pivot;
return low;
}
int findKthLargest(vector<int>& nums, int k) {
int mid=-1, low=0, high=nums.size()-1;
while(true){
mid = partition(nums,low,high);
if(mid==nums.size()-k){
return nums[mid];
}else if(mid<nums.size()-k){
low=mid+1;
}else{
high=mid-1;
}
}
}
};
使用优先队列,用法见:c++优先队列(priority_queue)用法详解
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
// 默认是大顶堆,使用greater<T> 是小顶堆
priority_queue<int,vector<int>,greater<int>> q;
for(auto it:nums){
q.push(it);
// pop掉最小的,剩下k个最大的,最后的top()就是k个最大中的最小值
if(q.size()>k) q.pop();
}
return q.top();
}
};
python解法:
from queue import PriorityQueue
class Solution:
def findKthLargest(self, nums: List[int], k: int) -> int:
pq=PriorityQueue()
for x in nums:
pq.put(x)
if pq.qsize()>k:
pq.get()
return pq.get()
判断链表是否有环以及环的位置,证明
class Solution:
def hasCycle(self, head: ListNode) -> bool:
if not head or not head.next: # 空链表或只有一个节点时没有环
return False
slow,fast=head,head
while fast and fast.next:
slow=slow.next
fast=fast.next.next
if slow==fast:
break
return slow==fast
证明寻找环的起点的算法:
http://www.bnee.net/article/20526.html
1、慢指针slow走到了环入口时,设共走了k步。此时快指针fast越过了环入口的步数为delta。因为快指针可能绕着环走了很多圈,所以有k = delta + n * R,其中R为环的大小,n为快指针绕环走的圈数。
2、证明必然会相遇。慢指针进入环中后,因为快指针每次都比慢指针快一步,所以,快慢指针最后一定会相遇。
3、计算快慢指针相遇位置。因为慢指针在刚进入环时距离快指针delta步,所以快指针还需要比慢指针多走R - delta步才能与慢指针相遇。又因为快指针每次走两步,所以快指针还需要走2(R - delta)步。那么,相对环的起点而言,相遇位置为2(R - delta) + delta = 2R - delta,即,距离环入口delta处,与慢指针刚进入环时快指针所在位置对称。
4、快指针重新从头结点开始走,速度为一次一步,与慢指针相同。可知,快指针走到环入口时,所需步数为k。由于 k = delta + n * R,所以慢指针也是刚好走到环入口。
class Solution:
def detectCycle(self, head: ListNode) -> ListNode:
if not head or not head.next:
return None
slow,fast=head,head
while fast and fast.next:
slow=slow.next
fast=fast.next.next
if slow==fast:
break
if slow==fast:
fast=head
while slow!=fast:
slow=slow.next
fast=fast.next
return slow
return None
青蛙跳台阶+有一次后退机会
面经看到,但是搜索不到这道题的解法,我目前的个人思路是这样的:
先当做没有后退机会来算,然后考虑一次后退机会加在不同位置带来的新的跳法总共多少种。
设有级台阶,分别标号,我们一开始处于位台阶 ,最后要到达位置.
当没有后退机会时,设从台阶 到达台阶 共有的跳法种数是 ,则
然后考虑一次后退机会加在不同位置带来的新的跳法总共多少种
如果后退之后到达的是台阶 ,那么产生新的跳法数是 “从台阶 到台阶 的跳法种数”,这应该等价于 “从台阶 到台阶 的跳法种数”,也就是 .
由上可知,考虑上后退位置的不同,
也就是按照没有后退机会求出前面所定义的 数组后,对数组求和再加上 即为所求,用python就是return dp[n] + sum(dp)
最长公共子串、最长公共子序列
二叉树路径最大和
https://leetcode.com/problems/binary-tree-maximum-path-sum/
蓄水池采样问题
https://www.jianshu.com/p/7a9ea6ece2af
https://blog.csdn.net/anshuai_aw1/article/details/88750673
给定一个数据流,数据流长度N很大,且N直到处理完所有数据之前都不可知,请问如何在只遍历一遍数据(O(N))的情况下,能够随机选取出k个不重复的数据。
这个场景强调了3件事:
- 数据流长度N很大且不可知,所以不能一次性存入内存。
- 时间复杂度为O(N)。
- 随机选取k个数,每个数被选中的概率为k/N。
思路:
1、前 k 个数据直接放入蓄水池。
2、蓄水池装满了 k 个数据之后,当接收到第 i 个数据时,在 [0, i] 范围内取随机数 r,若 r 落在[0, k-1]范围内,则用接收到的第i个数据替换蓄水池中的第 r 个数据。
3、重复步骤2。
核心代码如下:
int[] reservoir = new int[k];
// init
for (int i = 0; i < reservoir.length; i++){
reservoir[i] = dataStream[i];
}
for (int i = k; i < dataStream.length; i++){
// 随机获得一个[0, i]内的随机整数
int r = rand.nextInt(i + 1);
// 如果随机整数落在[0, m-1]范围内,则替换蓄水池中的元素
if (r < k){
reservoir[r] = dataStream[i];
}
}
注:这里使用已知长度的数组dataStream来表示未知长度的数据流,并假设数据流长度大于蓄水池容量m。
证明:
先求第 个数 在第 步之后被保留在蓄水池的概率 。在第 步时,它被保留的概率是 ,在第 步时,被保留的概率是 ,以此类推,可知:
再求第 个数 在第 步之后被保留在蓄水池的概率 。最后要保留必须在遇到它时将该数用于替换蓄水池中的数据,而且之后的每一步他都没有被替换掉。在第 步时,该数被保留下来的概率是 ,在第 步时,被保留在蓄水池的概率是 ,以此类推,可知:
综上可知,每个数在第 步之后被保留的概率都是
2 sum,3 sum
2sum的思路和python代码:
先扫描一次数组建立一个hash_map,存放key为某个数,value为其在nums中出现的下标(可能出现多次,所以value是list)
然后再扫一遍这个数组,对于每个num[i],判断target-nums[i]是否存在,如果存在则说明有,不存在继续找。
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
num2ids={} # 某个数在nums中出现的下标(可能出现多次,所以value是list)
for i in range(len(nums)):
if nums[i] in num2ids:
num2ids[nums[i]].append(i)
else:
num2ids[nums[i]]=[i]
for i in range(len(nums)):
a=nums[i] # 固定a,则我们要看 target-a 是否在nums中出现
b=target-a
if b==a: # 要注意的是如果 target-a 就等于 a自己,则a需要在nums出现两次
if a in num2ids and len(num2ids[a])>1:
return num2ids[a][:2] # 返回a出现的前两次下标
else:
if b in num2ids and len(num2ids[b])>0:
return [i]+[num2ids[b][0]] # 返回a出现的下标和b出现的下标
3sum,复杂度,参考:这个帖子
class Solution(object):
def threeSum(self, nums):
res = []
nums.sort()
length = len(nums)
for i in range(length - 2): # 从前n-2个数字中固定a,来找b和c
if i > 0 and nums[i] == nums[i - 1]:
continue # 已经选过这个数字作为a
l, r = i + 1, length - 1 # 在a的右边范围的左右端点开始夹逼找b和c
while l < r:
total = nums[i] + nums[l] + nums[r]
if total < 0: # 如果求和比0小,说明需要更大的b
l += 1
elif total > 0: # 如果求和比0小,说明需要更小的c
r -= 1
else: # 找到了一对组合
res.append([nums[i], nums[l], nums[r]])
# 在a固定的情况下,不可能再有一对数与刚找到的有相同的b,所以跳过所有相同的b
while l < r and nums[l] == nums[l + 1]:
l += 1
l += 1 # 前面while得到的是最右边的b的下标,下次需要换个b,所以要再右移一步
# 在a固定的情况下,不可能再有一对数与刚找到的有相同的c,所以跳过所有相同的c
while l < r and nums[r] == nums[r - 1]:
r -= 1
r -= 1 # 前面while得到的是最左边的c的下标,下次需要换个c,所以要再左移一步
return res
一个排序数组能够构成多少个二叉搜索树
https://leetcode.com/problems/unique-binary-search-trees/
分解质因数
def get_num_factors(num):
res = []
tmp = 2
if num <= 3:
return [num]
while num >= tmp:
if num % tmp == 0:
res.append(tmp)
num = num // tmp # 更新
else:
tmp = tmp + 1 # 同时更新除数值,不必每次都从头开始
return res
接雨水
题目链接:LC42. Trapping Rain Water
# 参考:https://zhuanlan.zhihu.com/p/79811305
class Solution:
def trap(self, height: List[int]) -> int:
n=len(height)
left_max=[0]*n # 第i号柱子左边最高的柱子高度
right_max=[0]*n # 第i号柱子右边最高的柱子高度
# 第0号柱子左边最高为0,从第1号柱子开始求
for i in range(1,n):
# 比较第i-1号柱子的高度与第i-1号柱子左边最高柱子的高度
left_max[i]=max(height[i-1],left_max[i-1])
# 第n-1号柱子右边最高为0,从第n-2号柱子开始求
for i in range(n-2,-1,-1):
right_max[i]=max(height[i+1],right_max[i+1])
volume=0
for i in range(n):
# 对于每一个柱子,求出左右两边最高柱子的短板
# 它限制了当前柱子上方能容纳多少雨水
low=min(left_max[i],right_max[i])
if low>height[i]:
volume+=low-height[i]
return volume
旋转数组的最小数字
题目链接:旋转数组的最小数字
# -*- coding:utf-8 -*-
class Solution:
def minNumberInRotateArray(self, arr):
# write code here
if len(arr)==0:
return 0
low , high = 0, len(arr) - 1
while low<high:
mid=(high-low)//2+low
if arr[mid]<arr[high]:
# 说明mid到high是递增的,最小元素肯定不在mid的右边,必然在mid或mid的左边,例如[4,5,1,2,3]或[1,2,3,4,5]
high=mid
elif arr[mid]>arr[high]:
# 此时mid肯定位于旋转数组的左半部分,例如[3,4,5,1,2],此时最小元素肯定在mid右边
low=mid+1
else:
# 当arr[mid]==arr[high]时,无法确定最小元素位于mid左边还是右边,只好一个一个找
# 例如:[1,0,1,1,1]和[1,1,1,0,1]
# 由于arr[high]==arr[mid],因此至少可以将high缩小一位(最多到达mid的位置)
high-=1
return arr[low]
最小的K个数
使用大顶堆求解,注意python中的PriorityQueue默认是小顶堆,若要使用大顶堆,可以将 put(x)
改为 put(-x)
,并将 get()
改为 -get()
# -*- coding:utf-8 -*-
try:
from queue import PriorityQueue # python3,若确定是py3可直接写这句
except:
from Queue import PriorityQueue # python2,若确定是py2可直接写这句
class MaxHeap:
def __init__(self):
self.pq = PriorityQueue()
def put(self, x):
self.pq.put(-x)
def get(self):
return -self.pq.get()
def empty(self):
return self.pq.empty()
def qsize(self):
return self.pq.qsize()
class Solution:
def GetLeastNumbers_Solution(self, tinput, k):
if len(tinput)*k == 0 or k > len(tinput):
return []
pq = MaxHeap()
for x in tinput:
if pq.qsize() < k:
pq.put(x)
else:
e = pq.get()
x = min(x, e)
pq.put(x)
res = []
while not pq.empty():
res.append(pq.get())
return res[::-1]