• LeetCode (32): Longest Valid Parentheses


    链接: https://leetcode.com/problems/longest-valid-parentheses/

    【描述】

    Given a string containing just the characters '(' and ')', find the length of the longest valid (well-formed) parentheses substring.

    For "(()", the longest valid parentheses substring is "()", which has length = 2.

    Another example is ")()())", where the longest valid parentheses substring is "()()", which has length = 4.

    【中文描述】

    给定一个String, 它只含有"("和")"两种字符,要求求出其中最长的合法组合。例子上面有。

    ————————————————————————————————————————————————————————————

     

    【初始思路】

    首先,确定一下,要用stack,这个是毋庸置疑的。不用stack不是不可以,但是会很累。

    其次,用递归是肯定可以的。但是,递归太慢,太耗空间。

    那么,用什么办法解决?

    仔细研究了一下,所谓合法()组合,是有一个绝对正确的性质的:

         在合法的串里,  每个 ')' 都能在之前找到一个 '(' 与之对应!

    我们的解法就依据这句话来做了。怎么弄呢?

    既然上面的说了,前提是在合法串里。那我们就想办法把合法串找出来嘛!大家肯定要吐槽了,你都找出来了,那长度还不好算?其实仔细想想确实不好算,因为我们这里实际上只是用stack把string从左往右撸了一遍。当前字符如果属于合法串,就给它一个标记。但是很显然,仅凭这些标记是无法确定具体串是什么样子,起止点在哪的。

     

    等有了标记后,我们就可以根据上面的性质来算长度了。

     

    好,上面是大致思路,我们来看看具体怎么给标记。

    首先,创建一个和string等长的的int[]. 用来记录每一位的标记。

    然后,遍历一遍string。那么,对于每个字符,有2种情况:

    (1)当遇到'('的时候,入栈,标记给0。因为很显然,仅仅有一个'('我怎么知道你后面会不会有')'来配对呢, 所以我先给你一个0吧。

    (2)当遇到')'的时候,应该从栈里出栈一个元素。此时还有2种情况:

         (2.1) 栈已经是空的了。那显然,这个')'在前面找不到和它配对的。直接给0;

         (2.2) 栈里还有元素,出栈成功。  这个时候, 说明这个')'在前面肯定有一个和它能够配对的。位置我可能不知道在哪,但是是肯定存在的。好,我们就给这个')'一个2作为标记。用来表示,这个')'属于一个配对组,并且一个配对组肯定是2个字符嘛。

    此外,还要考虑边界情况。字符串第一个元素特殊对待一下,如果是'('自然是标记0,如果是')',那也给标记为0(显然不能配对嘛)。

     

    好,到了这里,我们势必能得到一个int数组,里面只有2种数字: 0 和 2。

    接下来怎么找呢?

    根据前面那条重要的性质,在合法串里,每个2在前面必然会有一个0和它对应。那么,我们从后往前遍历一遍,给2一个计数器嘛,遇到2就加1,遇到0就减1嘛。而当遇到0且此时计数器已经为0的时候,说明遇到了不能配对的'('或')',那么此时合法串肯定被这个"害群之马"打断了,我们之前的配对就只能告一段落。把之前的这个合法串的长度更新到max里,然后2的计数器置为0,接着遍历。直到结束。

     

    遍历结束后,我们实际上就肯定能够得到最大的合法串的配对数了嘛,然后return max * 2不就好了么。

     

    【Show me the Code!!!】

     1 public static int longestValidParentheses(String s) {
     2         if (s == null || s.length() == 0 || s.length() == 1) {
     3             return 0;
     4         }
     5         int n = s.length();
     6         int[] d = new int[n];
     7 
     8         Stack<Character> stack = new Stack<Character>();
     9 
    10         if (s.charAt(0) == '(') {
    11             stack.push(s.charAt(0));
    12             d[0] = 0;
    13         } else {
    14             d[0] = 0;
    15         }
    16         for (int i = 1; i < n; i++) {
    17             if (s.charAt(i) == '(') {
    18                 stack.push(s.charAt(i));
    19                 //最后一位是'('的特殊情况
    20                 d[i] = 0;
    21             } else {
    22                 if (stack.size() == 0 ) {
    23                     //说明当前不匹配
    24                     //当前置为0
    25                     d[i] = 0;
    26                 } else {
    27                     stack.pop();
    28                     d[i] = 2;
    29                 }
    30             }
    31         }
    32 
    33         /**
    34          * 整理d[], 现在d[]里只有0和2
    35          * 从后往前, 对2计数, 遇到2就加1, 遇到0就-1;再维护一个总2的个数.遇到2就加1
    36          * 当计数<0的时候,2计数清0, 比较max和当前2总数. 需要时更新max
    37          * 当循环彻底结束后, return 2的总个数 * 2 即为所求
    38          */
    39         int twoCount = 0;
    40         int totalTwo = 0;
    41         int max = 0;
    42         int i = d.length - 1;
    43         while ( i >= 0) {
    44             if (d[i] == 0) {
    45                 twoCount--;
    46                 if (twoCount < 0 || i == 0) {
    47                     twoCount = 0;
    48                     if (totalTwo > max) {
    49                         max = totalTwo;
    50                     }
    51                     totalTwo = 0;
    52                 }
    53                 i--;
    54             } else {
    55                 twoCount++;
    56                 totalTwo++;
    57                 i--;
    58             }
    59         }
    60         return max * 2;
    61     }
    longestValidParentheses

    虽然是AC了,而且也是O(n)的时间复杂度,但是总觉得这个方法挺笨的,而且其实遍历了两遍,速度肯定慢。能否只遍历一遍解决呢?

     

    回想一下,上面之所以遍历了两遍,是因为在第一次遍历的时候我们无法计算长度,因为我们存的仅仅只是字符。那么换个思路,我们能否在stack里存位置。位置和位置之间肯定是能通过相减算出位置差的,位置差其实不就是长度么?

    这个时候,出入栈规则和上面就需要有一定差异了,我们来分析一下。

    '('的位置入栈没有问题,而遇到')'的时候,有2种可能性:

    (1)栈顶是一个'(',那不用说了,'('出栈,然后用当前位置 i 减去'('出栈后的栈顶位置,就是当前长度;

    (2)栈顶一个')',那么当前的这个')'怎么处理?跳过?我们用下面例子来看一下:

                                  (    )    )    (    (     )     )

                                  0   1   2    3    4    5    6

    首先遇到0入栈,再遇到1,0出栈,此时栈为空,没有栈顶元素了,所以我们首先在栈里放一个-1. 为什么放-1而不放别的值呢。其实是为了方便计算从最开始位置到当前位置的总长度。此时,相当于0出栈后,栈里还有一个-1. 然后1-(-1)=2, 当前最长长度是2,没问题。

    接下来,遇到2位置,是个')',我们先不管它,直接continue。接下来,3入栈,遇到4,也入栈。

    然后,遇到5,4出栈,5-3 = 2。最大长度是2,也没问题。接下来遇到6,3出栈,此时栈里只有-1了, 然后6-(-1) = 7 ,最大长度更新为7,显然不对。

    出错的原因在于,按照道理来讲,其实在2的位置是有一个')'存在,而我们没有加入栈中去。这时,如果遇到上面的情况,那么4和3分别出栈后,直接暴露了栈底给程序。相当于从后往前全算进去了。这显然是不对的。换句话说,有')'在的时候,我们可以帮助程序判断从哪里应该截断重新计算长度。所以,遇到')'而前一个位置不是'('的时候,或者当前栈顶就是-1的时候,')'也应该入栈。

     

    这样的话,从前遍历到后,一遍就可以算出最大长度来,我们来看看代码:

    【Show me the Code!!!】

     1 public int longestValidParentheses(String s) {
     2         Stack<Integer> st = new Stack<Integer>();
     3         st.push(-1);
     4         int len = 0;
     5         for(int i=0;i<s.length();i++){
     6              if(s.charAt(i)==')'&&st.peek()!=-1&&s.charAt(st.peek())=='('){
     7                 st.pop();
     8                 len = Math.max(len,i-st.peek());
     9             }
    10             else   st.push(i);
    11         }
    12         return len;
    13     }
    longestValidParentheses2

    这个代码就干净、简洁了很多!而且速度上来讲,对于短s的情况下,会比上面的算法快一倍!

     

    【DP】

    DP解法还在消化中,稍后更新。

  • 相关阅读:
    elasticsearch索引的增删改查入门
    windows下mongodb安装与使用
    在SqlServer中使用Try Catch(转)
    sqlserver 存储过程 try catch TRANSACTION (转)
    SQLserver2008如何把表格变量传递到存储过程中
    SQL对字符串数组的处理
    SQLSERVER数据库表各种同步技术
    函数参数
    闭包
    枚举
  • 原文地址:https://www.cnblogs.com/lupx/p/leetcode-32.html
Copyright © 2020-2023  润新知