282. 给表达式添加运算符

方法一:递归 eval

class Solution:
    def addOperators(self, num: str, target: int) -> List[str]:
        def dfs(exp, i, pre): 
            # exp: 表达式,i:索引,pre:前一个数(串)。
            if i == len(num):
                if eval(exp) == target: # 通过 eval 计算表达式的值,速度很慢。
                    res.append(exp)
                return

            for op in ["+", "-", "*"]: 
                dfs(exp + op + num[i], i+1, num[i])
           
            if pre[0] != '0':  # 直接连接前面的数字,不能有前导 0
                dfs(exp + num[i], i+1, pre + num[i])

        res = []
       
        dfs(num[0], 1, num[0])
        return res

方法二:递归

n = len(num) ,构建表达式,可以往 num 中间的 n−1 个空隙添加 + 号、- 号或 * 号,或者不添加符号。

用「回溯法」来模拟这个过程。从左向右构建表达式,并实时计算表达式的结果。由于乘法运算优先级高于加法和减法运算,还需要保存最后一个连乘串(如 234)的运算结果。

定义递归函数 backtrack(expr,i,res,mul),其中:

  • expr 为当前构建出的表达式;
  • i 表示当前的枚举到了 num 的第 i 个数字;
  • res 为当前表达式的计算结果;
  • mul 为表达式最后一个连乘串的计算结果。

该递归函数分为两种情况:

如果 i=n,说明表达式已经构造完成,若此时有 res=target,则找到了一个可行解,将 expr 放入答案数组中,递归结束;
如果 i<n,需要枚举当前表达式末尾要添加的符号(+ 号、- 号或* 号),以及该符号之后需要截取多少位数字。设该符号之后的数字为 \val,按符号分类讨论:

  • 若添加 + 号,则 res 增加 val,且 val 单独组成表达式最后一个连乘串;
  • 若添加 - 号,则 res 减少val,且 −val 单独组成表达式最后一个连乘串;
  • 若添加 * 号,由于乘法运算优先级高于加法和减法运算,我们需要对 res 撤销之前 mul 的计算结果,并添加新的连乘结果 mul∗val,也就是将 res 减少 mul 并增加 mul∗val。

代码实现时,为避免字符串拼接所带来的额外时间开销,采用字符数组的形式来构建表达式。此外,运算过程中可能会产生超过 32 位整数的结果,我们要用 64 位整数存储中间运算结果。

class Solution:
    def addOperators(self, num: str, target: int) -> List[str]:
        def dfs(exp, i, pre, cur):
            if i == (m:=len(num)):
                if cur == target:                    
                    res.append(exp)
                return 
            for j in range(1, m-i+1):
                t = num[i:i+j]
                if t[0] == "0" and len(t) > 1: return # break
                n = int(t)
                if i == 0:
                    dfs(t, j, n, n)
                    continue 
                dfs(exp+'+'+t, i+j, n, cur+n)
                dfs(exp+'-'+t, i+j, -n, cur-n)
                dfs(exp+'*'+t, i+j, pre*n, cur-pre + pre*n) 
                
        res = []
        dfs("", 0, 0, 0)
        return res

295. 数据流的中位数

方法一:有序插入

from sortedcontainers import SortedList
class MedianFinder:
    def __init__(self):
        #self.nums = []
        self.nums = SortedList()
        
    def addNum(self, num: int) -> None:
        # 插入后排序
        #self.nums.append(num)
        #self.nums.sort()

        # 使用 bisect 有序插入
        #bisect.insort(self.nums,num)

        # 二分有序插入
        #n = len(self.nums)
        #l, r = 0, n
        # while l < r:
        #     mid = (l + r)//2
        #     if num > self.nums[mid]:
        #        l = mid + 1
        #     else:
        #        r = mid 
        #self.nums.insert(r,num)
        
        # 使用 SortedList 
        self.nums.add(num)

    def findMedian(self) -> float:
        # n = len(self.nums)
        return self.nums[n//2] if (n:=len(self.nums))%2 else (self.nums[n//2]+self.nums[n//2-1]) / 2

方法二:对顶堆

大根堆维护比中位数小的数,小根堆维护比中位数大的数。

若 num 比中位数大(小根堆堆顶元素),则将其插入小根堆,插入后若小根堆长度大于大根堆长度,则将其堆顶转移到大根堆;否则将其插入大根堆。保证大根堆长度比小根堆长度多1或者相等。中位数要么在大根堆堆顶,要么就是取两个堆的堆顶求平均。

from sortedcontainers import SortedList
class MedianFinder:
    def __init__(self):
        """
        initialize your data structure here.
        """
        self.lo = [] # 大根堆
        self.hi = [] # 小根堆

    def addNum(self, num: int) -> None:   
        heapq.heappush(self.lo, -num)
        heapq.heappush(self.hi, -heapq.heappop(self.lo))

        if len(self.lo) < len(self.hi): 
            heapq.heappush(self.lo, -heapq.heappop(self.hi)) 
                 
    def findMedian(self) -> float:
        return -self.lo[0] if len(self.lo) > len(self.hi) else (-self.lo[0] + self.hi[0]) / 2
class MedianFinder:
    def __init__(self):
        self.left = []
        self.right = []

    def addNum(self, num: int) -> None:
        l, r, = self.left, self.right

        if not l or num <= -l[0]: # left 为空或 num <= 中位数(大根堆堆顶元素)  
            heapq.heappush(l, -num)
            
            if len(l) - len(r) > 1:
                heapq.heappush(r, -heapq.heappop(l)) # 转移小堆顶到大堆

        else:
            heapq.heappush(r, num)

            if len(r) > len(l):
                heapq.heappush(l, -heapq.heappop(r))

    def findMedian(self) -> float:       
        return -self.left[0] if len(self.left) > len(self.right) else  (-self.left[0]+self.right[0])/2

300. 最长递增子序列

方法一:动态规划

状态定义: dp[i] 是以 nums[i] 结尾的最长子序列长度。

转移方程:
dp[i] = max(dp[i], dp[j] + 1) for j in range(i) if nums[i] > nums[j]
说明:x in nums 在 x 前面所有小于 x 的元素中,找最大 dp。

初始状态:
dp = [1] * len(nums)

返回值:

dp 列表最大值

nums = [0,1,0,3,2,3]

Leetcode 281~300_小根堆

class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        if not nums: return 0
        n = len(nums)
        dp = [1] * n
       
        for i, x in enumerate(nums):            
            for j in range(i):
                if x > nums[j]:
                    dp[i] = max(dp[i], dp[j] + 1)
        return max(dp)

方法二:贪心 + 二分查找

简单的贪心,要使上升子序列尽可能的长,则需上升尽可能慢,每次加上的数尽可能的小。

nums = [3,5,6,2,5,4,19,5,6,7,12]
res = [3,5,6]
对 2 找到大于 2 的最小值 3 替换,res = [2,5,6]
对 5 前面正好 包含 跳过
对 4 找到大于 4 的最小值 5 替换,res = [2,4,6]
res = [2,4,6,19]
对 5 找到大于 5 的最小值 6 替换,res = [2,4,5,19]
对 6 找到大于 6 的最小值 19 替换,res = [2,4,5,6]
res = 【2,4,5,6,7,12】

nums = [4,10,4,3,8,9],[4,10],[3,10],【3,8,9】 
nums = [10,9,2,5,3,7,101,18],[10],[9],[2,5],[2,3],[2,3,7],[2,3,7,101],【2,3,7,18】
nums = [4,4,4,4,4],【4】
nums = [0,1,0,3,2,3],[0,1],[0,1,3],[0,1,2],【0,1,2,3】
nums = [1,3,6,7,9,4,10,5,6],[1,3,6,7,9],[1,3,4,7,9],[1,3,4,7,9,10],[1,3,4,5,9,10],[1,3,4,5,6,10],【1, 3, 4, 5, 6, 10】

注意:res 不一定是最长递增子序列

class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        if not nums: return 0        
        res, lengh = [nums[0]], 1        
        for x in nums[1:]: 
            if x > res[-1]:
                res.append(x)
                lengh += 1
            else:
                idx = bisect.bisect_left(res,x)
                if res[idx] != x:
                    res[idx] = x # 替换会影响 x > res[-1] 条件的判断

        return lengh