Day 71 DP: 132. 分割回文串 II, 673. 最长递增子序列的个数

132. 分割回文串 II

  • 思路
    • example
    • 最少分割次数
      • 分为两步骤

dp[i][j], 区间DP, s[i,...,j]是否回文串
f[i]: 以ith结尾的子串的 最少分割次数
f[i] = min(f[i], f[j] + 1), 如果s[j+1,...,i]是回文串 (相当于枚举最后一个分割位置

  • 复杂度. 时间:O(n^2), 空间: O(n^2)
class Solution:
    def minCut(self, s: str) -> int:
        n = len(s)  
        dp = [[True] * n for _ in range(n)]
        for i in range(n-1, -1, -1):
            for j in range(i+1, n):
                dp[i][j] = (s[i] == s[j]) and dp[i+1][j-1]
        f = [n+1] * n
        f[0] = 0   
        for i in range(1, n):
            if dp[0][i] == True:
                f[i] = 0 # no need to cut
            else: # 枚举最后一cut的位置: j后面
                for j in range(i):
                    if dp[j+1][i] == True:
                        f[i] = min(f[i], f[j] + 1)
        return f[n-1]
class Solution:
    def minCut(self, s: str) -> int:
        n = len(s) 
        # Step 1: dp[i][j]
        dp = [[False for _ in range(n)] for _ in range(n)] 
        for i in range(n-1, -1, -1):
            for j in range(i, n):
                if s[i] == s[j]:
                    if j - i <= 1:
                        dp[i][j] = True 
                    else:
                        dp[i][j] = dp[i+1][j-1] 
                else:
                    dp[i][j] = False 
        # Step 2: f[i] = min(f[i], f[j]+1) if dp[j+1][i] == True 
        f = [n-1 for _ in range(n)]
        f[0] = 0 
        for i in range(1, n):
            if dp[0][i] == True: # !!!
                f[i] = 0 # no need to cut
            else:
                for j in range(i):
                    if dp[j+1][i] == True:
                        f[i] = min(f[i], f[j] + 1)
        return f[n-1]

673. 最长递增子序列的个数

  • 思路
    • example
    • 复习 LIS: #300 长度
      • 常规单串DP, 时间O(n^2)
class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        dp = [0] * len(nums)
        dp[0] = 1
        for i in range(1, len(nums)):
            dp[i] = 1
            for j in range(i):
                if nums[i] > nums[j]:
                    dp[i] = max(dp[i], dp[j] + 1)
        return max(dp)
  • 二分优化 时间:O(n \log(n)), 空间: O(n)

d[i]: 长度为i的LIS的最后一个元素的最小可能值。这样就能构造尽可能长的递增子序列。
初始化d[0] = float('-inf')
d是严格递增的

class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        def BinarySearch_insert(d, target): # 第一个>=位置,左边界
            left, right = 0, len(d)-1
            while left <= right:
                mid = left + (right - left) // 2
                if d[mid] > target:
                    right = mid - 1
                elif d[mid] < target:
                    left = mid + 1
                else: # nums[mid] == target
                    right = mid - 1
            return left  # it's possible to have left = len(d) + 1
        n = len(nums)
        d = [float('-inf')]
        res = 0
        for i in range(n):
            # 在d中找最小的index使得d[index] >= nums[i], 二分找左边界
            insert_idx = BinarySearch_insert(d, nums[i])
            if insert_idx == len(d):
                d.append(nums[i])
                res += 1
            else:
                d[insert_idx] = nums[i]
        return res
  • d[i]: 长度为i+1的LIS的最后一个元素的最小可能值。
class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        def BinarySearch(nums, target):
            n = len(nums)
            left, right = 0, n-1
            while left <= right:
                mid = left + (right - left) // 2
                if nums[mid] < target:
                    left = mid + 1
                else:
                    right = mid - 1
            return left 
        n = len(nums)
        stack = [] # [nums[0]]
        for i in range(n):
            idx = BinarySearch(stack, nums[i]) 
            if idx == len(stack):
                stack.append(nums[i]) 
            else:
                stack[idx] = nums[i] 
        return len(stack) 
  • 这里#673 目标:个数

dp[i] 表示以 nums[i] 结尾的最长上升子序列的长度
cnt[i] 表示以 nums[i] 结尾的最长上升子序列的个数
目标:sum(cnt[i] if dp[i] == max_length)

  • 复杂度. 时间:O(n^2), 空间: O(n)
class Solution:
    def findNumberOfLIS(self, nums: List[int]) -> int:
        n = len(nums)
        dp = [1] * n  # 以i结尾最长LIS的长度
        cnt = [1] * n # 以i结尾最长LIS的个数
        max_length = 1 # 最长LIS的长度
        for i in range(1, n):
            for j in range(i): 
                if nums[i] > nums[j]:
                    if dp[j]+1 > dp[i]: # 更新以i结尾的最长LIS的 长度/个数
                        dp[i] = dp[j] + 1
                        cnt[i] = cnt[j]
                    elif dp[j]+1 == dp[i]:
                        # dp[i] = dp[j] + 1 # 可省去这行,因为以i结尾的最长LIS 没有变化 
                        cnt[i] += cnt[j]
                    # else: 不需要考虑
            if dp[i] > max_length:
                max_length = dp[i]
        # 最后统计累加
        res = 0
        for i in range(n):
            if dp[i] == max_length:
                res += cnt[i]
        return res 
class Solution:
    def findNumberOfLIS(self, nums: List[int]) -> int:
        # dp[i] 长度
        # cnt[i] 个数
        n = len(nums) 
        dp = [1 for _ in range(n)] 
        cnt = [1 for _ in range(n)]
        # Step 1: update dp[i] and cnt[i]
        for i in range(1, n):
            for j in range(i):
                if nums[i] > nums[j]:
                    if dp[j]+1 > dp[i]:
                        cnt[i] = cnt[j] 
                    elif dp[j]+1 == dp[i]:
                        cnt[i] += cnt[j] 
                    dp[i] = max(dp[i], dp[j]+1)
        max_ = max(dp) 
        # Step 2 
        res = 0
        for i in range(n): 
            if dp[i] == max_:
                res += cnt[i] 
        return res 
  • 二分优化
class Solution:
    def findNumberOfLIS(self, nums: List[int]) -> int:
        # d[i] = [*, *, *], both d (increasing) and d[i] (decreasing) are ordered, keep history
        # https://leetcode.cn/problems/number-of-longest-increasing-subsequence/solutions/1007075/zui-chang-di-zeng-zi-xu-lie-de-ge-shu-by-w12f/
        # cnt[i][j]: 以数字d[i][j]结尾的LIS的个数,目标cnt[n-1][-1]. 实际:前缀和
        def bisect(start, end, func):
            left, right = start, end 
            while left <= right:
                mid = left + (right - left) // 2
                if func(mid): 
                    right = mid - 1
                else:
                    left = mid + 1
            return left 
        n = len(nums) 
        d, cnt = [], [] 
        d = []
        for num in nums:
            idx = bisect(0, len(d)-1, lambda x: d[x][-1] >= num) 
            c = 1
            if idx > 0:
                idx2 = bisect(0, len(d[idx-1])-1, lambda x: d[idx-1][x] < num)
                c = cnt[idx-1][-1] - cnt[idx-1][idx2] 
            if idx == len(d):
                d.append([num]) 
                cnt.append([0, c]) 
            else: 
                d[idx].append(num) 
                cnt[idx].append(cnt[idx][-1] + c)
        return cnt[-1][-1]
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容