云计算百科
云计算领域专业知识百科平台

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

文章目录

  • 前言
  • 一、单词拆分
    • 1.题目
    • 2.代码
    • 3.例子
  • 二、最大递增子序列
    • 1.题目
    • 2.代码
    • 3.例子
  • 三、乘积最大子数组
    • 1.题目
    • 2.代码
    • 3.例子
  • 四、分割等和子集
    • 1.题目
    • 2.代码
    • 3.例子
  • 五、最长有效括号
    • 1.题目
    • 2.代码
    • 3.例子

前言

本文记录力扣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.代码

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. 初始化准备

// 把字典转成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.代码

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 值的最大值。

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

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

    • 先初始化 dp[1] = 1(单个元素的基础长度)。
    • 对比前面所有元素(仅j=0,nums[0]=10):9 < 10,不满足“递增”条件,无法继承j=0的子序列长度。
    • 因此 dp[1] 保持1,maxans 仍为1。
  • 处理第三个元素(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。
  • 处理第四个元素(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。
  • 处理第五个元素(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(没有超过当前最大值)。
  • 处理第六个元素(i=5,nums[5]=7)

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

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

    • 初始化 dp[7] = 1。
    • 依次对比前面的元素:
      • j=0到j=5:18分别大于10、9、2、5、3、7,其中最大的候选长度是 dp[5]+1=3+1=4,dp[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.代码

    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=2、mn=2
    • 计算当前 maxF:对比 2*(-5)=-10、-5、2*(-5)=-10,取最大值 -5
    • 计算当前 minF:对比 2*(-5)=-10、-5、2*(-5)=-10,取最小值 -10
    • 溢出检查:minF=-10 未超出int范围,无需重置
    • 更新 ans:对比 (int)-5 和 2,ans 仍为 2
    • 此时:maxF=-5,minF=-10,ans=2

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

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

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

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

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

    • 保存上一轮的 mx=8、mn=-80
    • 计算当前 maxF: 对比 8*3=24、3、(-80)*3=-240 → 取最大值 24
    • 计算当前 minF: 对比 8*3=24、3、(-80)*3=-240 → 取最小值 -240
    • 溢出检查:minF=-240 正常
    • 更新 ans:对比 24 和 20,ans 更新为 24
    • 此时:maxF=24,minF=-240,ans=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.代码

    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]为例

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

  • 数组长度n=4,满足≥2,不触发返回;
  • 遍历数组求和sum=1+5+11+5=22,找最大值maxNum=11;
  • sum=22是偶数,target=22/2=11;
  • maxNum=11等于target,不触发返回,进入动态规划逻辑。
  • 动态规划数组初始化 定义4行12列的boolean型dp数组(行对应前i+1个元素,列对应目标和0~11),初始全为false:

  • 所有行的第0列设为true(任何元素组合都能凑出和为0,选空集即可),即dp[0][0]、dp[1][0]、dp[2][0]、dp[3][0] = true;
  • 初始化第一个元素: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.代码

    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)。


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

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 力扣Hot100系列20(Java)——[动态规划]总结(下)( 单词拆分,最大递增子序列,乘积最大子数组 ,分割等和子集,最长有效括号)
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!