343. 整数拆分
方法一:动态规划
当 时,可以拆分。令 是拆分出的第一个正整数,则剩下的部分是
表示将正整数
0 和 1 都不能拆分,因此 。
当 时,假设对正整数 拆分出的第一个正整数是 ,则有以下两种方案:
将 拆分成 和 的和, 不再拆分,乘积是 ;
将 拆分成 和 的和, 继续拆分,乘积是 。
因此,当 固定时,有 。
状态转移方程:
最终得到
class Solution:
def integerBreak(self, n: int) -> int:
dp = [0] * (n + 1)
for i in range(2, n + 1):
for j in range(i):
dp[i] = max(dp[i], j * (i - j), j * dp[i - j])
return dp[n]
方法二:优化的动态规划
class Solution:
def integerBreak(self, n: int) -> int:
if n < 4: return n - 1
dp = [0] * (n + 1)
dp[2] = 1
for i in range(3, n + 1):
dp[i] = max(2 * (i - 2), 2 * dp[i - 2], 3 * (i - 3), 3 * dp[i - 3])
return dp[n]
方法三:数学
class Solution:
def integerBreak(self, n: int) -> int:
if n <= 3:
return n - 1
quotient, remainder = n // 3, n % 3
if remainder == 0:
return 3 ** quotient
elif remainder == 1:
return 3 ** (quotient - 1) * 4
else:
return 3 ** quotient * 2
352. 将数据流变为多个不相交区间
56. 合并区间 合并所有重叠的区间
class Solution:
def merge(self, intervals: List[List[int]]) -> List[List[int]]:
intervals.sort()
# s, e = intervals[0]
# res = []
# for a, b in intervals[1:]:
# if a > e:
# res.append([s, e])
# s = a
# e = max(e,b)
# res.append([s, e])
# return res
merged = []
for interval in intervals:
# 如果列表为空,或者当前区间与上一区间不重合,直接添加
if not merged or merged[-1][1] < interval[0]:
merged.append(interval)
else:
# 否则的话,我们就可以与上一区间进行合并
merged[-1][1] = max(merged[-1][1], interval[1])
return merged
435. 无重叠区间
class Solution:
def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:
if not intervals: return 0
intervals.sort(key=lambda x: x[1])
n = len(intervals)
right = intervals[0][1]
ans = 1
for s, e in intervals[1:]:
if s >= right:
ans += 1
right = e
return n - ans
本题是一道类似于区间合并的题目。
加入 val 分五种情况:
- 已经加过
- 孤点
- 正好可连着左侧
- 正好可连着右侧
- 正好可连接左右
class SummaryRanges:
def __init__(self):
self.x = [[-2, -2], [10004, 10004]] # 两哨兵
def addNum(self, val: int) -> None:
x = self.x
i = bisect.bisect_left(x,[val])
# 连左
if val <= x[i-1][1] + 1:
x[i-1][1] = max(x[i-1][1], val)
# 连右
if val >= x[i][0] - 1:
x[i][0] = val
# 连接左右
if x[i-1][1] == x[i][0]:
x[i-1:i+1] = [[x[i-1][0],x[i][1]]]
# 孤点插入
if x[i-1][1] + 1 < val < x[i][0] - 1:
x.insert(i, [val,val])
def getIntervals(self) -> List[List[int]]:
return self.x[1:-1]
一维
class SummaryRanges:
def __init__(self):
self.x = [-2, 10002] # 两哨兵
def addNum(self, val: int) -> None:
x = self.x
i = bisect.bisect_left(x, val)
if i % 2:
if val <= x[i-1] + 1: x[i-1] = val
if val >= x[i] - 1: x[i] = val
if x[i-1] == x[i]:
x[i-2:i+2] = [x[i-2], x[i+1]]
if x[i-1] + 1 < val < x[i] - 1:
x.insert(i, val)
x.insert(i+1,val)
def getIntervals(self) -> List[List[int]]:
x = self.x[1:-1]
return [[x[i-1],x[i]] for i in range(1,len(x),2)]
方法一、模拟
数据范围在
class SummaryRanges:
def __init__(self):
self.nums = [False] * 10001
def addNum(self, val: int) -> None:
self.nums[val] = True
def getIntervals(self) -> List[List[int]]:
res = []
start = end = -1
for i in range(10001):
if self.nums[i]:
if start == -1:
start = end = i
else:
end = i
elif start != -1:
res.append([start, end])
start = end = -1
# 最后一个元素可能有值
if start != -1:
res.append([start, end])
return res
class SummaryRanges:
def __init__(self):
self.nums = []
def addNum(self, val: int) -> None:
if val not in self.nums:
self.nums.append(val)
self.nums.sort()
# bisect.insort(self.nums, val)
def getIntervals(self) -> List[List[int]]:
# res = []
# head = tail = -1
# for i in self.nums + [-1]: # 末尾添加哨兵
# if head == -1:
# head = tail = i # 处理开头,固定左边,扩展右边。
# elif tail + 1 == i:
# tail = i
# else:
# res.append([head, tail])
# head = tail = i # 开辟新区间
# res.append([head, tail]) # 添加哨兵后不需要了
# 方法二:
res = []
if not self.nums:return []
head = tail = self.nums[0]
for i in self.nums[1:] + [-1]:
if i != tail + 1: # 如果不连续,分割区间。
res.append([head, tail])
head = tail = i
else:
tail = i
return res
# Your SummaryRanges object will be instantiated and called as such:
# obj = SummaryRanges()
# obj.addNum(val)
# param_2 = obj.getIntervals()
方法二、并查集
from sortedcontainers import SortedSet
class SummaryRanges:
def __init__(self):
self.father = [i for i in range(10002)] # 开辟一个 数组,记录区间的右边界。
self.points = SortedSet()
def addNum(self, val: int) -> None:
self.points.add(val)
self.father[val] = self.father[val + 1]
def getIntervals(self) -> List[List[int]]:
res = []
for p in self.points:
if not res or p > res[-1][1]:
res.append([p, self.find(p) - 1])
return res
def find(self, x):
if x == self.father[x]:
return x
self.father[x] = self.find(self.father[x])
return self.father[x]
并查集:
主要用于解决一些元素分组的问题。它管理一系列不相交的集合,并支持两种操作:
- 合并(Union):把两个不相交的集合合并为一个集合。
- 查询(Find):查询两个元素是否在同一个集合中。
并查集的重要思想在于,用集合中的一个元素代表集合。
初始化:
def __init__(self):
self.father = [i for i in range(n)]
假如有编号为 1, 2, 3, …, n 的 n 个元素,用一个数组 father 来存储每个元素的父节点。一开始,先将它们的父节点设为自己。
查询
def find(self, x):
if x == self.father[x]:
return x
return self.find(self.father[x])
我们用递归的写法实现对代表元素的查询:一层一层访问父节点,直至根节点(根节点的标志就是父节点是本身)。要判断两个元素是否属于同一个集合,只需要看它们的根节点是否相同即可。
合并
def merge(self, i, j):
self.father[self.find(i)] = self.find(j)
合并操作也是很简单的,先找到两个集合的代表元素,然后将前者的父节点设为后者即可。当然也可以将后者的父节点设为前者,这里暂时不重要。本文末尾会给出一个更合理的比较方法。
路径压缩
只要我们在查询的过程中,把沿途的每个节点的父节点都设为根节点即可。下一次再查询时,我们就可以省很多事。这用递归的写法很容易实现:
合并(路径压缩)
def find(x):
if x == fa[x]:
return x
else:
fa[x] = find(fa[x]) # 父节点设为根节点
return fa[x] # 返回父节点
# return x if x == fa[x] else find(fa[x])
路径压缩优化后,并查集的时间复杂度已经比较低了,绝大多数不相交集合的合并查询问题都能够解决。然而,对于某些时间卡得很紧的题目,我们还可以进一步优化。
按秩合并
可能有一个误解,以为路径压缩优化后,并查集始终都是一个菊花图(只有两层的树的俗称)。但其实,由于路径压缩只在查询时进行,也只压缩一条路径,所以并查集最终的结构仍然可能是比较复杂的。
我们用一个数组rank[]记录每个根节点对应的树的深度(如果不是根节点,其rank相当于以它作为根节点的子树的深度)。一开始,把所有元素的rank(秩)设为1。合并时比较两个根节点,把rank较小者往较大者上合并。
路径压缩和按秩合并如果一起使用,时间复杂度接近 [公式] ,但是很可能会破坏rank的准确性。
初始化(按秩合并)
def __init__(self):
self.father = [i for i in range(n)]
self.rank[i] = [1 for i in range(n)]
合并(按秩合并)
def find(x):
x = find(i); y = find(j) # 先找到两个根节点
if rank[x] <= rank[y]:
fa[x] = y
else
fa[y] = x
if rank[x] == rank[y] and x != y:
rank[y] += 1 # 如果深度相同且根节点不同,则新的根节点的深度+1