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

img

题目:

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

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

抛砖引玉

暴力解法

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

img

  • 设置开始与结束的指针来切分字符串
  • 双层循环,符合要求的字符会在截取过程中出现
    • 单数长度一定不符合要求,不进行是否符合校验
    • 长度小于已经出现符合要求的字符,不进行是否符合校验
  • 判断字符串是否符合要求
    • 如果某个元素是’(‘下一个元素是’)’,把这两个字符移出,之后重新循环
    • 符合要求的字符串会在校验中移除完,有剩余元素则说明不符合要求
/**
 * @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,记录时遇到’('则不作处理
  • 跳过了’(’,再累加,得到的累计数都应该是偶数
()(())XX
dp[0]dp[1]dp[2]dp[3]dp[4]dp[5]dp[i-1]dp[i]
02002dp[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;
};

博客: 小书童博客

公号: 坑人的小书童

坑人的小书童

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
探险家小扣的行动轨迹,都将保存在记录仪中。expeditions[i] 表示小扣第 i 次探险记录,用一个字符串数组表示。其中的每个「营地」由大小写字母组成,通过子串 -> 连接。例:"Leet->code->Campsite",表示到访了 "Leet"、"code"、"Campsite" 三个营地。expeditions[0] 包含了初始小扣已知的所有营地;对于之后的第 i 次探险(即 expeditions[i] 且 i > 0),如果记录中包含了之前均没出现的营地,则表示小扣 新发现 的营地。 请你找出小扣发现新营地最多且索引最小的那次探险,并返回对应的记录索引。如果所有探险记录都没有发现新的营地,返回 -1。注意: 大小写不同的营地视为不同的营地; 营地的名称长度均大于 0。用python实现。给你几个例子:示例 1: 输入:expeditions = ["leet->code","leet->code->Campsite->Leet","leet->code->leet->courier"] 输出:1 解释: 初始已知的所有营地为 "leet" 和 "code" 第 1 次,到访了 "leet"、"code"、"Campsite"、"Leet",新发现营地 2 处:"Campsite"、"Leet" 第 2 次,到访了 "leet"、"code"、"courier",新发现营地 1 处:"courier" 第 1 次探险发现的新营地数量最多,因此返回 1。示例 2: 输入:expeditions = ["Alice->Dex","","Dex"] 输出:-1 解释: 初始已知的所有营地为 "Alice" 和 "Dex" 第 1 次,未到访任何营地; 第 2 次,到访了 "Dex",未新发现营地; 因为两次探险均未发现新的营地,返回 -1
04-23
我的理解是,你想让我编写一段 Python 代码,实现一个算法,用于在给定的探险记录中找到发现新营地最多且索引最小的探险记录。 那么首先,我需要解析题目中的输入输出要求。 输入要求: - expeditions:一个字符串数组,表示小扣第一次到访的所有营地,和之后的每次探险记录(一个记录就是一个字符串,其中营地通过子串 -> 连接)。 输出要求: - 如果所有的探险记录都没有发现新的营地,则返回 -1; - 否则,返回发现新营地最多且索引最小的探险记录的索引。注:探险记录的索引从 0 开始计数。 接下来,我可以考虑如何编写代码来解决这个问题。 我的思路是,首先用一个集合记录所有已知的营地名称,然后遍历每个探险记录,依次将其中的营地名称加入到集合中,并判断是否有新的营地被发现。 如果某个探险记录中发现了新的营地,那么我就记录下发现了多少个新营地,以及这个探险记录的索引。最后,遍历所有的探险记录,找到发现新营地最多且索引最小的记录。 代码如下: ```python def find_new_camp(expeditions): known_camps = set() # 已知的营地集合 max_new_camps = 0 # 最多的新营地数量 max_new_camps_index = -1 # 最多新营地数量的探险记录的索引 for i in range(len(expeditions)): sub_camps = expeditions[i].split("->") # 提取探险记录中所有的营地名称 num_new_camps = 0 # 记录这个探险记录发现的新营地数量 for camp in sub_camps: if camp not in known_camps: # 如果这个营地是新的,就记录一下 known_camps.add(camp) num_new_camps += 1 if num_new_camps > max_new_camps: # 如果发现了更多的新营地,就更新最大值和索引 max_new_camps = num_new_camps max_new_camps_index = i return max_new_camps_index if max_new_camps > 0 else -1 ``` 我使用了两个变量 `max_new_camps` 和 `max_new_camps_index` 来记录已经遍历过的探险记录中,发现了最多的新营地数量,以及具有最多新营地数量的探险记录的索引。然后我遍历所有的探险记录,统计其中发现的新营地数量,如果大于当前的最大值,就更新 `max_new_camps` 和 `max_new_camps_index` 变量。最后,如果发现了新的营地,就返回 `max_new_camps_index`。如果所有的探险记录都没有发现新营地,就返回 -1。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值