一天一大 leet(最长有效括号)难度:困难-Day20200704

一天一大 leet(最长有效括号)难度:困难-Day20200704_第1张图片

题目:

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

示例

  • 示例1
输入: "(()"
输出: 2
解释: 最长有效括号子串为 "()"
  • 示例2
输入: ")()())"
输出: 4
解释: 最长有效括号子串为 "()()"

抛砖引玉

暴力解法

超时(217 / 230 个通过测试用例)

一天一大 leet(最长有效括号)难度:困难-Day20200704_第2张图片

  • 设置开始与结束的指针来切分字符串
  • 双层循环,符合要求的字符会在截取过程中出现
    • 单数长度一定不符合要求,不进行是否符合校验
    • 长度小于已经出现符合要求的字符,不进行是否符合校验
  • 判断字符串是否符合要求
    • 如果某个元素是’(‘下一个元素是’)’,把这两个字符移出,之后重新循环
    • 符合要求的字符串会在校验中移除完,有剩余元素则说明不符合要求
/**
 * @param {string} s
 * @return {number}
 */
var longestValidParentheses = function(s) {
  let len = s.length;
  let start = 0;
  let end = len;
  let _result = 0;
  while (start < len) {
      while (end > start) {
          if ((end - start + 1) % 2 && (end - start) > _result) {
              let str = s.slice(start, end)
              if (!match_str(str)) {
                  _result = Math.max(_result, end - start);
              }
          }
          end--
      }
      start++
      end = len;
  }
  return _result
  function match_str(str) {
      let i = 0;
      while (i < str.length - 1) {
          if (str[i] === '(' && str[i + 1] === ')') {
              str = str.replace('()', '');
              i = 0
          } else {
              i++
          }
      }
      return str.length
  }
};

官方答案

动态规划

  • 记录遍历字符串记录以每一个元素结尾符合要求的字符长度
  • 其中任何一个有效的字符都不会以’(‘结尾,默认计数是0,记录时遇到’('则不作处理
  • 跳过了’(’,再累加,得到的累计数都应该是偶数
( ) ( ( ) ) X X
dp[0] dp[1] dp[2] dp[3] dp[4] dp[5] dp[i-1] dp[i]
0 2 0 0 2 dp[5] dp[i-1] dp[i]

求dp[5]:

  1. 如果dp[5]是’('则为0
  2. 如果dp[5]是’)’:
  • 前面有’('与其匹配
  • 前面无’('与其匹配
    判断有无匹配的逻辑:
  • 上一个组队完成的字符长度即:dp[4]
  • 当前指针的位置:5
    5 − d p [ 4 ] − 1 = 2 5-dp[4]-1 = 2 5dp[4]1=2

匹配

匹配位置前一组匹配字符的长度与这次匹配的长度的和:
d p [ 5 ] = d p [ 5 − d p [ 4 ] − 1 − 1 ] + ( 5 − ( 5 − d p [ 4 ] − 1 − 1 ) ) dp[5] = dp[5-dp[4]-1-1] + (5 -(5-dp[4]-1-1)) dp[5]=dp[5dp[4]11]+(5(5dp[4]11))
即:
d p [ 5 ] = d p [ 5 − d p [ 4 ] − 2 ] + ( d p [ 4 ] + 2 ) = 2 + ( 2 + 2 ) = 6 dp[5] = dp[5-dp[4]-2] + (dp[4]+2) = 2+(2+2) = 6 dp[5]=dp[5dp[4]2]+(dp[4]+2)=2+(2+2)=6
如果5变成i的话则:
如果 s [ i − d p [ i − 1 ] − 1 ] = = = ′ ( ′ s[i-dp[i-1]-1] === '(' s[idp[i1]1]===(,则:
d p [ i ] = d p [ i − d p [ i − 1 ] − 2 ] + ( d p [ i − 1 ] + 2 ) dp[i] = dp[i-dp[i-1]-2] + (dp[i-1]+2) dp[i]=dp[idp[i1]2]+(dp[i1]+2)

不匹配

那指针在该位置时得到的字符串是不满足条件的


  • 上面的例子中dp[4],即dp[i-1]是’)’,那如果i为2,dp[0]是’('时:
  • dp[0]前面没有已经组队完成的字符长度,但是他可以与dp[1]组队
    则,s[i-1]为’('时当前组队的长度应该是:
    d p [ i ] = d p [ i − 2 ] + 2 dp[i] = dp[i - 2] + 2 dp[i]=dp[i2]+2

取字符串指定位置字符可以使用’[]'也可以使用chartAt

边界处理:

  • i为1时,dp[i - 2]不存在,默认为0
  • i-dp[i-1]-2小于0时,dp[i-dp[i-1]-2]不存在,默认为0
/**
 * @param {string} s
 * @return {number}
 */
var longestValidParentheses = function(s) {
   let len = s.length,
       _result = 0;
   if (len === 0) return _result
   let dp = Array(len).fill(0);
   for (let i = 1; i < s.length; i++) {
       if (s.charAt(i) == ')') {
           if (s.charAt(i - 1) == '(') {
               dp[i] = (i >= 2 ? dp[i - 2] : 0) + 2;
           } 
           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;
           }
           _result = Math.max(_result, dp[i]);
       }
   }
   return _result
};

  • 像暴力方法的思路,借助索引计算符合规则的字符串长度
  • 不同的是,不需要循环截取字符
  • 从前到后,如果规则被打断,就从被打断位置从新开始接
  • 最终返回最长的字符长度

规则被打断

  • ‘(‘后面可以逐个为’(’,只有’)‘数量小于’(’,才算被打断
  • 新建一个数组(栈)来存贮那些可能存在匹配字符的元素索引(用于计算长度)

循环

  • 遇到’(’,其是起点的标记,存入

  • 遇到’)’,找最近的那个’('与其匹配(从待匹配数组中去掉最后一个元素索引)

    • 如果待匹配数组中没有元素了,说明之前的字符已经匹配完了,如果还有匹配,那这个指针所在的位置就是起点
    • 如果待匹配数组中还有没有匹配的元素,说明有个字符还没找到匹配项,此时,只需要关注从上个起点到这个指针位置匹配的字符串长度又增加了一个:待匹配数组最后一个是起点,指针是终点

    匹配字符从索引0开始,那么匹配0时他的起点为-1则,待匹配数组中默认存放-1

/**
 * @param {string} s
 * @return {number}
 */
var longestValidParentheses = function(s) {
  let maxans = 0;
  let stack = [-1];
  for (let i = 0; i < s.length; i++) {
      if (s.charAt(i) == '(') {
          stack.push(i);
          continue;
      } 
      stack.pop();
      if (!stack.length) {
          stack.push(i);
      } else {
          maxans = Math.max(maxans, i - stack[stack.length - 1]);
      }
  }
  return maxans;
};

正向逆向结合

  • '(‘与’)'分别做起点

先从左向右找其中:

  • '(‘数量大于’)‘则继续查找,之后可能多出的’('会被补全
  • ')‘数量小于’('则本轮计数停止,统计归零,匹配被打断
  • '(‘数量等于’)'则找到字符满足要求,
  • ‘(‘的计数再循环结束时可能大于’)’,即:left>right,记录长度是使用2*right,一组’()’

再从右向左找其中:

  • '(‘数量小于’)‘则继续查找,之后可能多出的’)'会被补全
  • ')‘数量大于’('则本轮计数停止,统计归零,匹配被打断
  • '(‘数量等于’)'则找到字符满足要求,记录长度
  • ‘)‘的计数再循环结束时可能大于’(’,即:right>left,记录长度是使用2*left,一组’()’

返回记录的最大值

/**
 * @param {string} s
 * @return {number}
 */
var longestValidParentheses = function(s) {
  let left = 0, 
  right = 0, 
  maxlength = 0;
  for (let i = 0; i < s.length; i++) {
      if (s.charAt(i) == '(') {
          left++;
      } else {
          right++;
      }
      if (left == right) {
          maxlength = Math.max(maxlength, 2 * right);
      } else if (right > left) {
          left = right = 0;
      }
  }
  left = right = 0;
  for (let i = s.length - 1; i >= 0; i--) {
      if (s.charAt(i) == '(') {
          left++;
      } else {
          right++;
      }
      if (left == right) {
          maxlength = Math.max(maxlength, 2 * left);
      } else if (left > right) {
          left = right = 0;
      }
  }
  return maxlength;
};

博客: 小书童博客

公号: 坑人的小书童

坑人的小书童

你可能感兴趣的:(一天一大leet,算法,数据结构,leetcode,javascript,前端)