三道动态规划题-最长的有效括号、组合总和I、组合总和II

三道动态规划题-最长的有效括号、组合总和I、组合总和II

一、最长的有效括号

简单来说,给一个字符串,返回一个最长的有效括号

eg:
input: "()()()))"
output: 6

eg:
input: "(())(("
output: 4

(1)解法

有效括号大概可以包括三种情况

情况1:

"()()"

并列的括号

这种需要的是监测")“,比如i的位置是”)“,那么i-1的位置如果是”(",就说明这个i位置长度为2,然后去看是否有i-2位置,然后加上i-2位置的数就是结果。因为每一个有括号都是这个操作,所以如果前面出现过右括号,那么一定被标记过最长的括号

情况2:

"(())"

嵌套的括号

有效括号的长度就为:

value(i-1) + (i-value(i-1)-1) == '(' ? 2: 0

就是判断最前面的括号是否可以构成有效的括号

情况3:

"(())()"

嵌套+并列

需要在判断i-1位置为做括号时候,加上左括号左边的那个值

2 + value(i-2)

(2)代码

public static int longestValidParentheses(String s){
  char[] chars = s.toCharArray();
  int[] dp = new int[s.length()];
  if ("".equals(s) || s.length() == 1) return 0;
  if (s.length() == 2) return chars[0] == '(' && chars[1] == ')' ? 2: 0;
  dp[0] = 0;
  dp[1] = chars[0] == '(' && chars[1] == ')' ? 2: 0;
  // 最大值为第二个括号的数值
  int max = dp[1];
  for (int i = 2; i < s.length(); i++) {
    if (chars[i] == ')'){
      // 判断左边是否为"("
      if (chars[i-1] == '('){
        dp[i] = dp[i-2] + 2;
      }
      // 为")",判断嵌套结构
      else if(i - dp[i-1] - 1 >= 0 && chars[i-dp[i-1]-1] == '('){
        // 处理嵌套并列结构
        // dp[i-1] + 2 : 嵌套结构
        // (i-dp[i-1]-1 > 0 ? dp[i-dp[i-1]-2]: 0) : 并列结构
        dp[i] = (i-dp[i-1]-1 > 0 ? dp[i-dp[i-1]-2]: 0)+ dp[i-1] + 2;
      }else{
        dp[i] = 0;
      }
      max = Math.max(max, dp[i]);
    }
  }
  return max;
}

二、组合总和 I

给定一组数字,不重复,给定一个target,有多少组合使得他们的和为target,数组中的数字可以重复使用

eg:
input: [2,3,4], 8
output: [2,2,2,2],[2,2,4],[2,3,3],[4,4]

(1) 解法

设定pos为当前索引, rest为当前还需要的target,那么在每一个pos的时候都有两个选择,

1.选择这个数字

2.不选择这个数字

那么我们就可以调用函数:

f(pos+1, rest)

f(pos, rest - value(pos))

函数有两个终止条件:

1.rest==0;说明已经得到了需要的结果

2.rest<value(pos)的时候,这个数字不可以选择

(2)代码

public static void dfs(int[] arr, int target, List<List<Integer>> ans, List<Integer> combine, int idx){
  // backend
  if (idx == arr.length) return;
  if (target == 0) ans.add(new ArrayList<>(combine));
  // 循环
  // 不选择
  dfs(arr, target, ans, combine, idx+1);
  // 选择,但是需要选择之后的数值大于target
  if (target - arr[idx] >= 0){
    // 把这个值加入combine
    combine.add(arr[idx]);
    dfs(arr, target - arr[idx], ans, combine, idx);
    combine.remove(combine.size() - 1);
  }
}

三、组合总和 II

给定一组数字,一个target,找出存在的组合使得相加为target,每个数字只能使用一次,而且数组存在重复值

eg: 
input: [1,2,4,1,2], 5
output: [1,4],[1,2,2]

(1)解法

和上个题目不同,使用简单的递归+回溯的时候,会存在有重复结果的情况,比如[3,4], target = 7,那么,得到的结果就是[3,4],[4,3],造成了重复

其实造成重复的结果主要是数据中有重复值,如果有一个方法能把重复的数值变成不重复的,那么就可以解决这个问题了

可以采用类似字典的操作,记录每个数值,并且记录数值出现的次数:

[2,2,4,2,3,5,4]
-- > 
[[2,3],[3,1],[4,2],[5,1]]

将数值当作数组的第一个元素,出现的次数当作数组的第二个元素,这样就解决了重复的问题

(2)代码

/**
* pos: 当前的位置
* rest: 剩余的目标值
* frep: 转换后的数组
*/
public void dfs3(int pos, int rest, List<int[]> frep, List<List<Integer>> ans, List<Integer> combine){
  // backend
  if (rest == 0) {
    ans.add(new ArrayList<Integer>(combine));
    return;
  }
  if (pos >= frep.size() || rest < frep.get(pos)[0]){
    return;
  }

  // 两种情况
  // 情况1:不选择
  dfs3(pos+1, rest, frep, ans, combine);
  // 情况2:选择
  // 可以选择多个,这个按照数值的个数,和选择之后不能够超过这个rest为准,最多选择most个
  int most = Math.min(rest / frep.get(pos)[0], frep.get(pos)[1]);
  // 将most个数字添加到combine中
  for (int i = 1; i <= most; i++) {
    combine.add(frep.get(pos)[0]);
    dfs3(pos+1, rest - frep.get(pos)[0] * i, frep, ans, combine);
  }
  // 选择下一个

  // 全部拿出来
  for (int i = 1; i <= most; i++) {
    combine.remove(combine.size() - 1);
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

牧码文

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值