343. 整数拆分

方法一:动态规划

Leetcode 341~360_List 时,可以拆分。令 Leetcode 341~360_List_02 是拆分出的第一个正整数,则剩下的部分是 Leetcode 341~360_List_03

Leetcode 341~360_python_04 表示将正整数 Leetcode 341~360_区间合并_05

0 和 1 都不能拆分,因此 Leetcode 341~360_动态规划_06

Leetcode 341~360_python_07 时,假设对正整数 Leetcode 341~360_区间合并_05 拆分出的第一个正整数是 Leetcode 341~360_python_09,则有以下两种方案:

Leetcode 341~360_区间合并_05 拆分成 Leetcode 341~360_区间合并_11Leetcode 341~360_区间合并_12 的和,Leetcode 341~360_区间合并_12 不再拆分,乘积是 Leetcode 341~360_python_14

Leetcode 341~360_区间合并_05 拆分成 Leetcode 341~360_区间合并_11Leetcode 341~360_区间合并_12 的和,Leetcode 341~360_区间合并_12 继续拆分,乘积是 Leetcode 341~360_python_19

因此,当 Leetcode 341~360_区间合并_11 固定时,有 Leetcode 341~360_python_21

状态转移方程:

Leetcode 341~360_动态规划_22

最终得到 Leetcode 341~360_区间合并_23

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 分五种情况:

  1. 已经加过
  2. 孤点
  3. 正好可连着左侧
  4. 正好可连着右侧
  5. 正好可连接左右
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)]

方法一、模拟

数据范围在 Leetcode 341~360_区间合并_24

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