算法()-最大、括号

最大

1.LeetCode-239 滑动窗口的最大值

方法三:分块 + 预处理

分块预处理:将数组从左到右按每 k 个元素分为一组 (最后一组元素可能会不是k个),分块数组 后 预处理产生两个 辅助数组。前缀最大值数组、后缀最大值数组。

前缀最大值数组,每个分块 index 从 0 -> k-1, 前缀block[0], block[0到1], block[0到2],… block[0到k-1]的最大值
后缀最大值数组,每个分块 index 从 k-1 -> 0, 后缀block[k-1], block[k-1到k-2], block[k-1到0]的最大值

使用 i 表示元素索引下标,当 i 从 1 到 n-1, 对应 k 个元素的区间 {num[i], num[i+k-1]} 可能 属于分块数组中的同一块,也可能横跨两个块。那么当前窗口的最大值 前缀最大值 和 后缀最大值其中大的那个。
在这里插入图片描述

class Solution:
    def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
        n = len(nums)
        prefix_array = [nums[0]] * n
        suffix_array = [nums[-1]] * n 
        for i in range(1, n):     # [1, 2, 3, n-1]

            if i % k == 0:      # i % k == 0, 刚好是每个分块的起始下标      
                prefix_array[i] = nums[i]
            else:
                prefix_array[i] = max(prefix_array[i-1], nums[i])
            
            j = n - 1 - i
            if (j+1) % k == 0:  # (j + 1) % k == 0, 每个分块的终止下标
                suffix_array[j] = nums[j]
            else:
                suffix_array[j] = max(suffix_array[j + 1], nums[j])

            # print(i, j, prefix_array, suffix_array)

        res = []
        for i in range(0, n - k + 1):  # [0, 1, 2, ...., n - k]
            res.append(max(suffix_array[i], prefix_array[i+k-1]))
        return res

[1,3,-1,-3,5,3,6,7]
1 6 8 [1, 3, 1, 1, 1, 1, 1, 1] [7, 7, 7, 7, 7, 7, 7, 7]
2 5 8 [1, 3, 3, 1, 1, 1, 1, 1] [7, 7, 7, 7, 7, 3, 7, 7]
3 4 8 [1, 3, 3, -3, 1, 1, 1, 1] [7, 7, 7, 7, 5, 3, 7, 7]
4 3 8 [1, 3, 3, -3, 5, 1, 1, 1] [7, 7, 7, 5, 5, 3, 7, 7]
5 2 8 [1, 3, 3, -3, 5, 5, 1, 1] [7, 7, -1, 5, 5, 3, 7, 7]
6 1 8 [1, 3, 3, -3, 5, 5, 6, 1] [7, 3, -1, 5, 5, 3, 7, 7]
7 0 8 [1, 3, 3, -3, 5, 5, 6, 7] [3, 3, -1, 5, 5, 3, 7, 7]
[1, 3, 3, -3, 5, 5, 6, 7] [3, 3, -1, 5, 5, 3, 7, 7]

2.LeetCode-53 最大和 连续子数组

# 动态规划数组的定义: dp[i] 最大和, 这个子数组是以 nums[i] 结尾的,dp[i] 只与 dp[i-1] 有关
# 状态转移方程: dp[i] = max(nums[i], nums[i] + dp[i-1])
# key1: 用 nums[i] 更新 dp[i] 时,nums[i] 可以成为已有数组的尾部,也可以成为单元素数组,这两种情况 nums[i] 都是作为子数组的结尾
# key2: 如果某一块数组的和为负数,这一块不会作为和最大连续子数组的结尾(多元素数组/单元素数组),因为去掉这个部分,后半段加和会更大。  空间约减,将空间复杂度降为o(1)
class Solution:
    def maxSubArray0(self, nums: List[int]) -> int:
    """
    @note 暴力法-遍历(n-1)*n/2个子数组,找出最大和
          求和次数减少:用空间换时间,已经算过的和可以用二维数组存起来,如下
    """
       n = len(nums)
       dp = [[0] * n for _ in range(n)]

       res = nums[0]     # len(nums) >= 1 的假设
       for i in range(n):
           sub_arr_sum[i][i] = nums[i]
           res = max(res, nums[i])
       
       for i in range(n-2, -1, -1):
           for j in range(i+1, n):
               sub_arr_sum[i][j] = sub_arr_sum[i][j-1] + nums[j]
               res = max(res, sub_arr_sum[i][j])
       return res

    def maxSubArray(self, nums: List[int]) -> int:
        """
        @note 一维 dp 
        # [-2, 1, -3, 4, -1, 2, 1]
        # i = 0, dp[0] = 0
        # i = 1, dp[1] = max(1, 1) = 1
        # i = 2, dp[2] = max(-3, 1+-3) = -2 
        # i = 3, dp[3] = max(4, 4+-2) = 4
        # i = 4, dp[4] = max(-1, 4 -1) = 3
        # i = 5, dp[5] = max(2, 2 + 3) = 5
        """
        n = len(nums)
        dp = [0] * n
        dp[0] = nums[0]
        res = dp[0]
        for i in range(1, n):
            dp[i] = max(nums[i], nums[i] + dp[i-1])
            res = max(res, dp[i])
        return res

    def maxSubArray2(self, nums: List[int]) -> int:
    	"""
		@note	 空间约减后,空间复杂度为0(1)
			dp 非负数 最大子数组和,如果为0,说明nums[i+1]更新dp数组时会重新开辟一个新的子数组
    	"""
        res = float("-INF")
        dp = 0
        for val in nums:
            dp += val        # [-1] 先加,更新答案,确定是否归零
            res = max(dp, res)
            dp = max(dp, 0)
        return res

3.LeetCode-152 乘积最大的子数组。

给你一个整数数组 nums ,请你找出数组中乘积最大的连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。

直接思路: f m a x ( i ) f_{max}(i) fmax(i)表示以第i个元素结尾的乘积最大的子数组的积,状态转移方程为
f m a x ( i ) = max ⁡ i = 1 n { f ( i − 1 ) ∗ a i , a i } f_{max}(i)=\max_{i=1}^n\{f(i-1)*a_i,a_i\} fmax(i)=i=1maxn{f(i1)ai,ai}
即, f m a x ( i ) f_{max}(i) fmax(i)可以考虑nums[i]加入前面 f m a x ( i − 1 ) f_{max}(i-1) fmax(i1)对应的一段,或者自成一段,这两种情况取最大值,求出所有的f_i之后,选取一个最大的作为结果。

核心问题:当前位置的最优解不一定是由前一个位置的最优解得到。因为存在正负数

如果当前的数是负数的话,我们希望以num[i-1]为结尾的某一个段的积也是一个负数,负负可以为正。

如果当前数为正,我们希望以num[i-1]为结尾的某一个段的积也是一个正数,正的越多,乘积完越大。

所以再维护一个 f m i n ( i ) f_{min}(i) fmin(i)表示以第i个元素结尾的乘积最小的子数组的积:
f m a x ( i ) = max ⁡ { f m a x ( i − 1 ) ∗ a i , f m i n ( i − 1 ) ∗ a i , a i } f m i n ( i ) = min ⁡ { f m a x ( i − 1 ) ∗ a i , f m i n ( i − 1 ) ∗ a i , a i } f_{max}(i)=\max\{f_{max}(i-1)*a_i,f_{min}(i-1)*a_i,a_i\}\\ f_{min}(i)=\min\{f_{max}(i-1)*a_i,f_{min}(i-1)*a_i,a_i\} fmax(i)=max{fmax(i1)ai,fmin(i1)ai,ai}fmin(i)=min{fmax(i1)ai,fmin(i1)ai,ai}

def maxProduct(self, nums):
    """
    :type nums: List[int]
    :rtype: int
    """
    pre_max, pre_min = 1, 1
    res = float("-INF")
    for val in nums:
        cur_max = max(pre_max * val, pre_min * val, val)
        cur_min = min(pre_max * val, pre_min * val, val)
        pre_max, pre_min = cur_max, cur_min
        res = max(res, cur_max)
    return res

4.剑指 Offer 14- I. 剪绳子为k个整数段,使各个段成绩最大

给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]…k[m-1] 。请问 k[0]k[1]…*k[m-1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。

DP

dp[i]: 长度为i 绳子至少剪了一次的最长长度
d p [ i ] = m a x ( d p [ j ] ∗ ( i − j ) , j ∗ ( i − j ) , d p [ i ] ) , j ∈ [ 1 , i − 1 ] dp[i] = max(dp[j]*(i-j),j*(i-j),dp[i]),j\in[1,i-1] dp[i]=max(dp[j](ij),j(ij),dp[i]),j[1,i1]

class Solution(object):
    def cuttingRope(self, n):
        """
        :type n: int
        :rtype: int
        """
        dp = [0]*(n+1)
        dp[1] = 1
        for i in range(2,n+1):
            for j in range(1,i):
                dp[i]= max(dp[i],dp[j]*(i-j),j*(i-j))
        return dp[-1]

n^2复杂度的DP

数学推导

通过数学不等式推到,可以得到,当每段长度为3时乘积最大。所以尽可能分为三段,最后一段依据具体情况判断:

class Solution(object):
    def cuttingRope(self, n):
        """
        :type n: int
        :rtype: int
        """
        if n<=3:
            return n-1
        s, mod = n //3, n % 3   
        if mod == 0:
            res = 3**s
        elif mod == 1:
            res = 3**(s-1)*4 
        else:
            res = 3**s*2
        return res%(10**9+7)

5.盛最多水的容器

给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。
找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。返回容器可以储存的最大水量。

class Solution:
    def maxArea1(self, height: List[int]) -> int:
    	"""
    	@note 暴力法,遍历所有可以盛水的容器, 49 / 61 个通过的测试用例
    	"""
        n = len(height)
       	vol_arrary = [[0] * n for _ in range(n)]
        res = 0
        for i in range(0, n):
            for j in range(i+1, n):
                w = j - i
                h = min(height[i], height[j])
                vol_arrary[i][j] = w * h
                res = max(res, vol_arrary[i][j])
        return res

    def maxArea2(self, height: List[int]) -> int:
        """
        @note 动态规划-key 在左右边界往里缩的时候,让短板边长,才有使盛水量变多的可能
			array[i][j] = w * h, h =  min(height[i], height[j])
	        每次移动短板,有效成水高度h   有变大的可能, 面积可能会变大
	        每次移动高板,有效成水高度h, 不变/变小      面积一定变小
        """
        n = len(height)
        left, right = 0, n-1
        res = 0
        while(left < right):

            vol = (right - left) * min(height[left], height[right])
            res = max(res, vol)

            if height[left] <= height[right]:
                left += 1
            else:
                right -= 1
        return res

括号

1.LeetCode-22 括号生成–各种括号排列组合

数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。

输入:n = 3
输出:[
       "((()))",
       "(()())",
       "(())()",
       "()(())",
       "()()()"
     ]

回溯法

def generateParenthesis(self, n):
    res = []
    def back_track(s,left,right):
        if len(s) == 2*n:
            res.append(s)
            return 
        if left < n:
            back_track(s+"(",left+1,right)
        if right< left:                # 保证不会生成不合理的括号 ,必须要有配对的左括号已经存在
            back_track(s+")",left,right+1)
    back_track("",0,0)
    return res

2.LeetCode-20 有效括号(是否)–堆栈

给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串,判断字符串是否有效。
有效字符串需满足:左括号必须用相同类型的右括号闭合。左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。

输入: "()[]{}"
输出: true

机理:合理的右括号,总能找到对应的左括号。多出左括号或者右括号都是不对的。多对括号复合,拿掉一对合理的括号,并不改变括号复合的合理性。

堆栈:栈顶匹配。左括号入栈,配对右括号,弹出对应的左括号;不配对右括号,入栈。遍历完字符串,查看栈是否为空,空则有效,非空,无效。

def isValid(self, s):
   dit={")":"(","]":"[","}":"{"}
   stack=[]
   for char in s:
       if char in dit:     # char为右括号
           left=stack.pop() if stack else "#"
           if dit[char]!=left:
               return False
       else:              # char 为左括号入栈
           stack.append(char)
	return True if len(stack) == 0 else False 

多括号行为,单括号可以直接用计数法

3.LeetCode-32 最长有效括号(长度)–dp

给定一个只包含 ‘(’ 和 ‘)’ 的字符串,找出最长的包含有效括号的子串的长度。

输入: "(()"
输出: 2
解释: 最长有效括号子串为 "()"

dp[i] 表示以下标 i字符结尾的最长有效括号的长度,(以s[i]结尾能构成的有效字符串的长度)依据s[i] 与之前的括号的配对情况,更行dp数组。
显然有效的子串一定以)结尾,因此我们可以知道以(结尾的子串对应的dp 值必定为 0,我们只需要求解 )在dp 数组中对应位置的值。

# 1.s[i]==")" and s[i-1]=="("    dp[i] = dp[i-2]+2
# 2.s[i]==")" and s[i-1]==")"    dp[i] = dp[i-1] + dp[i-dp[i-1]-2]+2 下标的合理性
def longestValidParentheses(self, s):
    n=len(s)
    if n<2:
        return 0
    dp=[0]*n
    res=0
    for i in range(1,n):
        if i==1:
            if s[i]==")" and s[i-1]=="(":
                dp[1]=2
        else:
            if s[i]==")" and s[i-1]=="(":
                dp[i]=dp[i-2]+2               
            if s[i]==")" and s[i-1]==")":
                if i-1-dp[i-1]>=0 and s[i-1-dp[i-1]]=="(":
                    dp[i]=dp[i-1]+2 # index 有效性没有验证
                    if i-2-dp[i-1]>=0:
                        dp[i]+=dp[i-2-dp[i-1]]
        res=max(res,dp[i]) 
    return res

4.LeetCode-301删除无效括号 --多种删除方式

删除最小数量的无效括号,使得输入的字符串有效,返回所有可能的结果。
说明: 输入可能包含了除 ( 和 ) 以外的字符。

输入: "()())()"
输出: ["()()()", "(())()"]

考虑所有的删除情况,采用广度优先,第一层为原字符串表达式,第二层为删除一个字符,第三层为删除两个字符的情况,不断广度优先遍历,直至找到第一个有效的删除数量,即为最少数量

DFS:要求删除的括号最少,每次删除一个,观察删除后的字符串是否合法,如果已经合法,不用继续删除。
BFS:本层level和下一层level 之间的关系:本层level每个元素都拿出来,列举删除一个括号后的所有可能,添加到下一层level 中。
在这里插入图片描述
解决重复性问题:吧level 中的list换成set
检查括号是否合法:堆栈法
用filter(func, param) 可以得到param中所有符合条件的元素。

class Solution(object):
    def removeInvalidParentheses(self, s):
        """
        :type s: str
        :rtype: List[str]
        """
        def is_valid(string):
            count = 0
            for char in string:
                if char == "(":
                    count += 1
                elif char == ")":
                    count -= 1
                if count < 0:     # 中途中计数器如果小于0说明,不明多余右括号出现
                    return False
            return count == 0
        # BFS
        level = {s}  # 用set避免重复
        while True:
            valid = list(filter(isValid, level))  # 判断同一层的所有删除结果时候存在有效备选
            if valid: return valid 
            # 下一层level
            next_level = set()
            for item in level:
                for i in range(len(item)):
                    if item[i] in "()":                     # 如果item[i]这个char是个括号就删了,如果不是括号就留着
                        next_level.add(item[:i]+item[i+1:])
            level = next_level
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值