力扣Hot100系列20(Java)——[动态规划]总结(下)( 单词拆分,最大递增子序列,乘积最大子数组 ,分割等和子集,最长有效括号)

文章目录


前言

本文记录力扣Hot100里面关于动态规划的五道题,包括常见解法和一些关键步骤理解,也有例子便于大家理解


一、单词拆分

1.题目

给你一个字符串 s 和一个字符串列表 wordDict 作为字典。如果可以利用字典中出现的一个或多个单词拼接出 s 则返回 true。

注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。

示例 1:

输入: s = "leetcode", wordDict = ["leet", "code"]

输出: true

解释: 返回 true 因为 "leetcode" 可以由 "leet" 和 "code" 拼接成。

示例 2:

输入: s = "applepenapple", wordDict = ["apple", "pen"]

输出: true

解释: 返回 true 因为 "applepenapple" 可以由 "apple" "pen" "apple" 拼接成。

注意,你可以重复使用字典中的单词。

示例 3:

输入: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]

输出: false

提示:

  • 1 <= s.length <= 300
  • 1 <= wordDict.length <= 1000
  • 1 <= wordDict[i].length <= 20
  • s 和 wordDict[i] 仅由小写英文字母组成
  • wordDict 中的所有字符串 互不相同

2.代码

java 复制代码
public class Solution {
    public boolean wordBreak(String s, List<String> wordDict) {
        // 1. 把单词列表转成HashSet:目的是快速判断某个子串是否在字典里(HashSet的contains方法时间复杂度O(1))
        Set<String> wordDictSet = new HashSet(wordDict);
        
        // 2. 定义dp数组:dp[i]表示s的前i个字符(s[0..i-1])能否被拆分,长度为s.length()+1(覆盖0~s.length())
        boolean[] dp = new boolean[s.length() + 1];
        
        // 3. 边界条件:dp[0] = true → 空字符串(前0个字符)可以被拆分(作为拆分的"起点")
        dp[0] = true;
        
        // 4. 外层循环:遍历字符串的所有长度,从1到s.length()(判断前1个、前2个...前n个字符能否拆分)
        for (int i = 1; i <= s.length(); i++) {
            // 5. 内层循环:遍历所有可能的拆分点j(把前i个字符拆成"前j个字符"+"j到i的子串")
            for (int j = 0; j < i; j++) {
                // 6. 判断条件:
                // - dp[j] = true → 前j个字符能被拆分(拆分的"前半段有效")
                // - wordDictSet.contains(s.substring(j, i)) → j到i的子串在字典里(拆分的"后半段有效")
                if (dp[j] && wordDictSet.contains(s.substring(j, i))) {
                    // 7. 只要找到一个有效的拆分点,就说明前i个字符能拆分,标记为true并跳出内层循环(不用再找其他拆分点)
                    dp[i] = true;
                    break;
                }
            }
        }
        
        // 8. 返回整个字符串(前s.length()个字符)能否拆分的结果
        return dp[s.length()];
    }
}

3.例子

输入

  • 字符串 s = "leetcode"
  • 单词字典 wordDict = ["leet", "code"]

预期输出true(因为 "leetcode" 可以拆分成 "leet" + "code")

1. 初始化准备

java 复制代码
// 把字典转成HashSet,方便快速查询
Set<String> wordDictSet = new HashSet<>(Arrays.asList("leet", "code"));
// 定义dp数组,长度为 8 + 1 = 9("leetcode"长度为8)
boolean[] dp = new boolean[9];
// 边界条件:空字符串可以被拆分
dp[0] = true;

此时 dp = [true, false, false, false, false, false, false, false, false]

2. 外层循环(i 从 1 到 8)

步骤 1:i = 1(判断前1个字符 "l" 能否拆分)

内层 j 从 0 到 0:

  • j=0:dp[0] = true,但子串 s[0,1] = "l" 不在字典中 → dp[1] 仍为 false

步骤 2:i = 2(判断前2个字符 "le" 能否拆分)

内层 j 从 0 到 1:

  • j=0:子串 "le" 不在字典 → 不满足
  • j=1:dp[1] = false → 不满足
    dp[2] = false

步骤 3:i = 3(判断前3个字符 "lee" 能否拆分)

内层 j 从 0 到 2:所有 j 对应的子串("lee"、"ee"、"e")都不在字典 → dp[3] = false

步骤 4:i = 4(判断前4个字符 "leet" 能否拆分)

内层 j 从 0 到 3:

  • j=0:dp[0] = true,子串 s[0,4] = "leet" 在字典中 → 满足条件!
    → 标记 dp[4] = true,跳出内层循环
    此时 dp[4] = true,其余仍为 false。

步骤 5:i = 5(判断前5个字符 "leetc" 能否拆分)

内层 j 从 0 到 4:

  • j=0:子串 "leetc" 不在字典
  • j=1:dp[1] = false
  • j=2:dp[2] = false
  • j=3:dp[3] = false
  • j=4:dp[4] = true,但子串 s[4,5] = "c" 不在字典 → 不满足
    dp[5] = false

步骤 6:i = 6(判断前6个字符 "leetco" 能否拆分)

内层 j 遍历到 4 时:dp[4] = true,但子串 s[4,6] = "co" 不在字典 → dp[6] = false

步骤 7:i = 7(判断前7个字符 "leetcod" 能否拆分)

内层 j 遍历到 4 时:dp[4] = true,但子串 s[4,7] = "cod" 不在字典 → dp[7] = false

步骤 8:i = 8(判断前8个字符 "leetcode" 能否拆分)

内层 j 从 0 到 7,重点看 j=4:

  • j=4:dp[4] = true(前4个字符"leet"能拆分),子串 s[4,8] = "code" 在字典中 → 满足条件!
    → 标记 dp[8] = true,跳出内层循环

3. 最终返回

返回 dp[8],结果为 true,符合预期。

二、最大递增子序列

1.题目

给你一个整数数组 nums ,找到其中最长严格递增子序列 的长度。

子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

示例 1:

输入:nums = [10,9,2,5,3,7,101,18]

输出:4

解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。

示例 2:

输入:nums = [0,1,0,3,2,3]

输出:4

示例 3:

输入:nums = [7,7,7,7,7,7,7]

输出:1

2.代码

java 复制代码
class Solution {
    public int lengthOfLIS(int[] nums) {
        // 边界条件:空数组直接返回0
        if (nums.length == 0) {
            return 0;
        }
        // dp数组:dp[i]表示以nums[i]为最后一个元素的最长递增子序列长度
        int[] dp = new int[nums.length];
        // 初始化:第一个元素自身构成长度为1的子序列
        dp[0] = 1;
        // maxans:记录全局最长长度
        int maxans = 1;
        
        // 遍历从第二个元素开始(i从1到末尾)
        for (int i = 1; i < nums.length; i++) {
            // 初始化:每个元素自身至少构成长度为1的子序列
            dp[i] = 1;
            // 遍历i之前的所有元素j,找能接在后面的子序列
            for (int j = 0; j < i; j++) {
                // 如果nums[i] > nums[j],说明可以接在nums[j]的子序列后面
                if (nums[i] > nums[j]) {
                    // 更新dp[i]:取当前值 和 dp[j]+1 的最大值
                    dp[i] = Math.max(dp[i], dp[j] + 1);
                }
            }
            // 更新全局最长长度
            maxans = Math.max(maxans, dp[i]);
        }
        // 返回全局最长长度
        return maxans;
    }
}

3.例子

输入nums = [10, 9, 2, 5, 3, 7, 101, 18]
核心规则dp[i] 表示以 nums[i] 为最后一个元素的最长递增子序列长度,初始值为1;若 nums[i] > nums[j](j < i),则 dp[i] = max(dp[i], dp[j]+1);最终结果是所有 dp 值的最大值。

  1. 处理第一个元素(i=0,nums[0]=10)

    • 这是第一个元素,没有前面的元素可以对比,所以 dp[0] = 1(自身构成长度为1的子序列)。
    • 此时全局最长长度 maxans = 1
  2. 处理第二个元素(i=1,nums[1]=9)

    • 先初始化 dp[1] = 1(单个元素的基础长度)。
    • 对比前面所有元素(仅j=0,nums[0]=10):9 < 10,不满足"递增"条件,无法继承j=0的子序列长度。
    • 因此 dp[1] 保持1,maxans 仍为1。
  3. 处理第三个元素(i=2,nums[2]=2)

    • 初始化 dp[2] = 1
    • 依次对比前面的元素:
      • j=0(nums[0]=10):2 < 10,不满足;
      • j=1(nums[1]=9):2 < 9,不满足;
    • 没有可继承的子序列,dp[2] 保持1,maxans 仍为1。
  4. 处理第四个元素(i=3,nums[3]=5)

    • 初始化 dp[3] = 1
    • 依次对比前面的元素:
      • j=0(10):5 < 10,不满足;
      • j=1(9):5 < 9,不满足;
      • j=2(2):5 > 2,满足条件 → 此时可以把5接在2的子序列后面,子序列长度变为 dp[2]+1 = 1+1=2
    • 因此 dp[3] 更新为2,maxans 随之更新为2。
  5. 处理第五个元素(i=4,nums[4]=3)

    • 初始化 dp[4] = 1
    • 依次对比前面的元素:
      • j=0(10)、j=1(9):3都更小,不满足;
      • j=2(2):3 > 2,满足 → 子序列长度为 dp[2]+1=2
      • j=3(5):3 < 5,不满足;
    • 因此 dp[4] 为2,maxans 仍为2(没有超过当前最大值)。
  6. 处理第六个元素(i=5,nums[5]=7)

    • 初始化 dp[5] = 1
    • 依次对比前面的元素:
      • j=0(10)、j=1(9):7更小,不满足;
      • j=2(2):7 > 2 → 候选长度为 1+1=2dp[5] 暂时更新为2;
      • j=3(5):7 > 5 → 候选长度为 2+1=3dp[5] 更新为3;
      • j=4(3):7 > 3 → 候选长度为 2+1=3,和当前dp[5]相等,无变化;
    • 因此 dp[5] 为3,maxans 更新为3。
  7. 处理第七个元素(i=6,nums[6]=101)

    • 初始化 dp[6] = 1
    • 依次对比前面的元素:
      • j=0(10):101 > 10 → 候选长度 1+1=2dp[6] 暂时为2;
      • j=1(9):101 > 9 → 候选长度 1+1=2,无变化;
      • j=2(2):101 > 2 → 候选长度 1+1=2,无变化;
      • j=3(5):101 > 5 → 候选长度 2+1=3dp[6] 更新为3;
      • j=4(3):101 > 3 → 候选长度 2+1=3,无变化;
      • j=5(7):101 > 7 → 候选长度 3+1=4dp[6] 更新为4;
    • 因此 dp[6] 为4,maxans 更新为4。
  8. 处理第八个元素(i=7,nums[7]=18)

    • 初始化 dp[7] = 1
    • 依次对比前面的元素:
      • j=0到j=5:18分别大于10、9、2、5、3、7,其中最大的候选长度是 dp[5]+1=3+1=4dp[7] 先更新为4;
      • j=6(101):18 < 101,不满足,无变化;
    • 因此 dp[7] 为4,maxans 保持4(没有超过当前最大值)。

最终结果

所有元素处理完毕后,dp 数组最终为 [1,1,1,2,2,3,4,4],取其中的最大值4,这就是最长递增子序列的长度。

三、乘积最大子数组

1.题目

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

测试用例的答案是一个 32-位 整数。

请注意,一个只包含一个元素的数组的乘积是这个元素的值

示例 1:

输入: nums = [2,3,-2,4]

输出: 6解释: 子数组 [2,3] 有最大乘积 6。

示例 2:

输入: nums = [-2,0,-1]

输出: 0

解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。

提示:

  • 1 <= nums.length <= 2 * 10(4)
  • -10 <= nums[i] <= 10
  • nums 的任何子数组的乘积都 保证 是一个 32-位 整数

2.代码

java 复制代码
class Solution {
    public int maxProduct(int[] nums) {
        // 1. 初始化:maxF表示以当前元素结尾的子数组的最大乘积,minF表示最小乘积
        // 用long是为了避免整数溢出(比如大数相乘超出int范围)
        long maxF = nums[0], minF = nums[0];
        // 2. 初始化答案为第一个元素(至少有一个元素时,最大乘积至少是它自己)
        int ans = nums[0];
        // 3. 获取数组长度,方便循环
        int length = nums.length;
        
        // 4. 从第二个元素开始遍历(i=1)
        for (int i = 1; i < length; ++i) {
            // 5. 保存上一轮的maxF和minF(因为后续会修改,必须先存)
            long mx = maxF, mn = minF;
            
            // 6. 更新当前maxF:有三种可能,取最大的
            // - 上一轮最大乘积 * 当前元素(正正/正负)
            // - 当前元素自己(重新开始一个子数组)
            // - 上一轮最小乘积 * 当前元素(负负得正)
            maxF = Math.max(mx * nums[i], Math.max(nums[i], mn * nums[i]));
            
            // 7. 更新当前minF:有三种可能,取最小的
            // - 上一轮最小乘积 * 当前元素(负正/负负)
            // - 当前元素自己(重新开始一个子数组)
            // - 上一轮最大乘积 * 当前元素(正负得负)
            minF = Math.min(mn * nums[i], Math.min(nums[i], mx * nums[i]));
            
            // 8. 处理溢出:如果minF小于int的最小值(-2^31),重置为当前元素
            // 这是一个额外的溢出保护,避免后续强转int时出错
            if(minF < - (1 << 31)){ // 1<<31 是 2^31,- (1<<31) 是int最小值
                minF = nums[i];
            }
            
            // 9. 更新全局答案:取当前maxF(强转int)和历史ans的最大值
            ans = Math.max((int)maxF, ans);
        }
        // 10. 返回最终的最大乘积
        return ans;
    }
}

3.例子

输入nums = [2, -5, -2, -4, 3]
核心目标 :找到连续子数组的最大乘积(最终答案是 (-2)*(-4)*3 = 24

1. 初始化

  • maxF = 2(以第一个元素2结尾的最大乘积)
  • minF = 2(以第一个元素2结尾的最小乘积)
  • ans = 2(全局最大乘积初始为第一个元素)

2. 处理第二个元素(i=1,nums[1]=-5)

  • 先保存上一轮的 mx=2mn=2
  • 计算当前 maxF:对比 2*(-5)=-10-52*(-5)=-10,取最大值 -5
  • 计算当前 minF:对比 2*(-5)=-10-52*(-5)=-10,取最小值 -10
  • 溢出检查:minF=-10 未超出int范围,无需重置
  • 更新 ans:对比 (int)-52ans 仍为 2
  • 此时:maxF=-5minF=-10ans=2

3. 处理第三个元素(i=2,nums[2]=-2)

  • 保存上一轮的 mx=-5mn=-10
  • 计算当前 maxF
    对比 (-5)*(-2)=10-2(-10)*(-2)=20 → 取最大值 20
  • 计算当前 minF
    对比 (-5)*(-2)=10-2(-10)*(-2)=20 → 取最小值 -2
  • 溢出检查:minF=-2 正常
  • 更新 ans:对比 202ans 更新为 20
  • 此时:maxF=20minF=-2ans=20

4. 处理第四个元素(i=3,nums[3]=-4)

  • 保存上一轮的 mx=20mn=-2
  • 计算当前 maxF
    对比 20*(-4)=-80-4(-2)*(-4)=8 → 取最大值 8
  • 计算当前 minF
    对比 20*(-4)=-80-4(-2)*(-4)=8 → 取最小值 -80
  • 溢出检查:minF=-80 正常
  • 更新 ans:对比 820ans 仍为 20
  • 此时:maxF=8minF=-80ans=20

5. 处理第五个元素(i=4,nums[4]=3)

  • 保存上一轮的 mx=8mn=-80
  • 计算当前 maxF
    对比 8*3=243(-80)*3=-240 → 取最大值 24
  • 计算当前 minF
    对比 8*3=243(-80)*3=-240 → 取最小值 -240
  • 溢出检查:minF=-240 正常
  • 更新 ans:对比 2420ans 更新为 24
  • 此时:maxF=24minF=-240ans=24

最终结果

遍历结束,返回 ans=24,和预期的最大子数组乘积(-2*-4*3=24)一致。

四、分割等和子集

1.题目

给你一个 只包含正整 数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等

示例 1:

输入:nums = [1,5,11,5]

输出:true

解释:数组可以分割成 [1, 5, 5] 和 [11] 。

示例 2:

输入:nums = [1,2,3,5]

输出:false

解释:数组不能分割成两个元素和相等的子集

2.代码

java 复制代码
    public boolean canPartition(int[] nums) {
        // 获取数组长度
        int n = nums.length;
        // 边界条件1:数组长度小于2,无法分割成两个子集
        if (n < 2) {
            return false;
        }

        // 计算数组总和sum,同时找出数组中的最大值maxNum
        int sum = 0, maxNum = 0;
        for (int num : nums) {
            sum += num;
            maxNum = Math.max(maxNum, num);
        }

        // 边界条件2:总和为奇数,无法分成两个和相等的子集
        if (sum % 2 != 0) {
            return false;
        }

        // 目标值:总和的一半(只要能凑出这个值,剩下的元素和也必然等于这个值)
        int target = sum / 2;
        // 边界条件3:最大值超过目标值,无法凑出(单个元素就超过一半,其他元素加起来也不够)
        if (maxNum > target) {
            return false;
        }

        // 动态规划数组:dp[i][j] 表示前i+1个元素(nums[0]~nums[i])能否凑出和为j
        boolean[][] dp = new boolean[n][target + 1];

        // 初始化:任何前i+1个元素都能凑出和为0(选空集即可)
        for (int i = 0; i < n; i++) {
            dp[i][0] = true;
        }

        // 初始化:只有第一个元素时,能凑出和为nums[0]
        dp[0][nums[0]] = true;

        // 遍历从第二个元素开始的所有元素
        for (int i = 1; i < n; i++) {
            int num = nums[i]; // 当前遍历到的元素值
            // 遍历所有可能的目标和(从1到target)
            for (int j = 1; j <= target; j++) {
                if (j >= num) {
                    // 情况1:j >= 当前元素,有两种选择:
                    // 1. 不选当前元素:结果继承dp[i-1][j](前i个元素能否凑出j)
                    // 2. 选当前元素:结果看dp[i-1][j-num](前i个元素能否凑出j-num,加上当前num就凑出j)
                    // 只要其中一种选择可行,dp[i][j]就为true
                    dp[i][j] = dp[i - 1][j] | dp[i - 1][j - num];
                } else {
                    // 情况2:j < 当前元素,无法选当前元素,直接继承上一轮结果, 因为肯定凑不出j,凑出来的一定大于j
                    dp[i][j] = dp[i - 1][j];
                }
            }
        }

        // 最终结果:前n个元素能否凑出和为target(总和的一半)
        return dp[n - 1][target];
    }
}

3.例子

以nums = [1,5,11,5]为例

前置处理(边界判断+基础值计算)

  1. 数组长度n=4,满足≥2,不触发返回;
  2. 遍历数组求和sum=1+5+11+5=22,找最大值maxNum=11;
  3. sum=22是偶数,target=22/2=11;
  4. maxNum=11等于target,不触发返回,进入动态规划逻辑。

动态规划数组初始化

定义4行12列的boolean型dp数组(行对应前i+1个元素,列对应目标和0~11),初始全为false:

  1. 所有行的第0列设为true(任何元素组合都能凑出和为0,选空集即可),即dp[0][0]、dp[1][0]、dp[2][0]、dp[3][0] = true;
  2. 初始化第一个元素:dp[0][1] = true(只有元素1,能凑出和为1),此时dp[0]行仅列0、1为true,其余为false。

遍历第二个元素(i=1,当前元素num=5)

逐个计算目标和j=1到11的dp[1][j]:

  • j=1:j<5,直接继承dp[0][1],dp[1][1]=true;
  • j=2-4:j<5,继承dp[0][j],均为false;
  • j=5:j≥5,dp[0][5](false)或 dp[0][0](true),结果为true;
  • j=6:j≥5,dp[0][6](false)或 dp[0][1](true),结果为true;
  • j=7-11:j≥5,继承/计算后均为false;
    最终dp[1]行true的列:0、1、5、6

遍历第三个元素(i=2,当前元素num=11)

逐个计算目标和j=1到11的dp[2][j]:

  • j=1-6:j<11,直接继承dp[1][j],即列0、1、5、6为true,其余为false;
  • j=7-10:j<11,继承dp[1][j],均为false;
  • j=11:j≥11,dp[1][11](false)或 dp[1][0](true),结果为true;
    最终dp[2]行true的列:0、1、5、6、11(此时已凑出目标和11)。

遍历第四个元素(i=3,当前元素num=5)

逐个计算目标和j=1到11的dp[3][j]:

  • j=1:j<5,继承dp[2][1]=true;
  • j=5:j≥5,dp[2][5](true)或 dp[2][0](true)=true;
  • j=6:j≥5,dp[2][6](true)或 dp[2][1](true)=true;
  • j=11:j≥5,dp[2][11](true)或 dp[2][6](true)=true;
  • 其余j按规则计算,不影响核心结果;
    最终dp[3]行true的列包含0、1、5、6、11

最终结果

返回dp[3][11],值为true,说明该数组可以分割为两个等和子集([11]和[1,5,5],和均为11)。

五、最长有效括号

1.题目

给你一个只包含 '(' 和 ')' 的字符串,找出最长有效 (格式正确且连续)括号 子串长度。

左右括号匹配,即每个左括号都有对应的右括号将其闭合的字符串是格式正确的,比如 "(()())"。

示例 1:

输入:s = "(()"

输出:2

解释:最长有效括号子串是 "()"

示例 2:

输入:s = ")()())"

输出:4

解释:最长有效括号子串是 "()()"

示例 3:

输入:s = ""

输出:0

2.代码

java 复制代码
class Solution {
    public int longestValidParentheses(String s) {
        int maxans = 0; // 全局最长有效括号长度
        // dp[i]:以s[i]结尾的最长有效括号长度
        int[] dp = new int[s.length()];
        
        for (int i = 1; i < s.length(); i++) {
            if (s.charAt(i) == ')') { // 仅处理右括号结尾的情况
                // 情况1:s[i-1]是'(',形成"()"基础配对
                if (s.charAt(i - 1) == '(') {
                    dp[i] = (i >= 2 ? dp[i - 2] : 0) + 2;
                } 
                // 情况2:s[i-1]是')',找嵌套匹配的'('
                else if (i - dp[i - 1] > 0 && s.charAt(i - dp[i - 1] - 1) == '(') {
                    dp[i] = dp[i - 1] + ((i - dp[i - 1]) >= 2 ? dp[i - dp[i - 1] - 2] : 0) + 2;
                }
                maxans = Math.max(maxans, dp[i]); // 更新最大值
            }
        }
        return maxans;
    }
}

3.例子

s = "()(())"为例(索引0到5)

前置初始化

  • 字符串:s[0]='(', s[1]=')', s[2]='(', s[3]='(', s[4]=')', s[5]=')'
  • maxans 初始为0,dp 数组长度6,初始全为0(dp[0]=dp[1]=...=dp[5]=0)。

遍历开始(i从1到5)

1. i=1(s[1]=')')

  • 条件:s[1]=')',进入判断;
  • 情况1:s[0]='('(基础"()"配对);
  • 计算dp[1]:i=1<2 → dp[1] = 0 + 2 = 2;
  • 更新maxans:max(0,2)=2;
  • 此时:dp=[0,2,0,0,0,0],maxans=2。

2. i=2(s[2]='(')

  • 条件:s[2]是'(',不处理;
  • dp[2]保持0,maxans仍为2;
  • 此时:dp=[0,2,0,0,0,0],maxans=2。

3. i=3(s[3]='(')

  • 条件:s[3]是'(',不处理;
  • dp[3]保持0,maxans仍为2;
  • 此时:dp=[0,2,0,0,0,0],maxans=2。

4. i=4(s[4]=')')

  • 条件:s[4]=')',进入判断;
  • 情况1:s[3]='('(基础"()"配对);
  • 计算dp[4]:i=4≥2 → dp[4] = dp[2](0) + 2 = 2;
  • 更新maxans:max(2,2)=2;
  • 此时:dp=[0,2,0,0,2,0],maxans=2。

5. i=5(s[5]=')',核心步骤)

  • 条件:s[5]=')',进入判断;
  • 情况1不满足(s[4]=')'),进入情况2;
  • 先算关键值:dp[i-1]=dp[4]=2;
  • 验证条件:i - dp[i-1] = 5-2=3 >0,且 s[3-1]=s[2]='('(匹配);
  • 计算dp[5]:
    • 第一部分:dp[4]=2;
    • 第二部分:i-dp[i-1]=3 ≥2 → dp[3-2]=dp[1]=2;
    • 第三部分:+2;
    • 总计:dp[5] = 2 + 2 + 2 = 6;
  • 更新maxans:max(2,6)=6;
  • 此时:dp=[0,2,0,0,2,6],maxans=6。

最终结果

遍历结束,返回maxans=6(对应整个字符串"()(())"是最长有效括号,长度6)。


如果本篇文章对您有帮助,可以点赞,收藏或评论哦!!!关注主包不迷路,让我们一起向前进步吧!!

相关推荐
末央&27 分钟前
【天机论坛】项目环境搭建和数据库设计
java·数据库
枫叶落雨22242 分钟前
ShardingSphere 介绍
java
花花鱼1 小时前
Spring Security 与 Spring MVC
java·spring·mvc
小白菜又菜1 小时前
Leetcode 2075. Decode the Slanted Ciphertext
算法·leetcode·职场和发展
言慢行善2 小时前
sqlserver模糊查询问题
java·数据库·sqlserver
专吃海绵宝宝菠萝屋的派大星2 小时前
使用Dify对接自己开发的mcp
java·服务器·前端
大数据新鸟2 小时前
操作系统之虚拟内存
java·服务器·网络
Tong Z2 小时前
常见的限流算法和实现原理
java·开发语言
凭君语未可2 小时前
Java 中的实现类是什么
java·开发语言
He少年2 小时前
【基础知识、Skill、Rules和MCP案例介绍】
java·前端·python