zoukankan      html  css  js  c++  java
  • [LeetCode] Longest Valid Parentheses -- 挂动态规划羊头卖stack的狗肉

    (Version 1.3)

    这题在LeetCode上的标签比较有欺骗性,虽然标签写着有DP,但是实际上根本不需要使用动态规划,相反的,使用动态规划反而会在LeetCode OJ上面超时。这题正确的做法应该和Largest Rectangle in Histogram那几个使用stack来记录并寻找左边界的题比较类似,因为在仔细分析问题并上手尝试解决时,会发现问题的关键在于怎么判定一个valid parentheses子串的起始位置,或者说当遇到一个')'时,怎么知道要加到哪里去。

    第一次做的时候因为标签是DP,所以写了一个无脑版本的DP,时间复杂度是O(N^2)的,结果不出意料得到了Time Limit Exceeded,代码如下,

     1 public class Solution {
     2     public int longestValidParentheses(String s) {
     3         if (s.length() < 2) {
     4             return 0;
     5         }
     6         int result = 0;
     7         boolean[][] isValid = new boolean[s.length()][s.length()];
     8         int len = s.length();
     9         for (int i = 0; i < isValid.length - 1; i++) {
    10             if (s.charAt(i) == '(' && s.charAt(i + 1) == ')') {
    11                 isValid[i][i + 1] = true;
    12                 result = 2;
    13             }
    14         }
    15         for (int l = 4; l <= len; l += 2) {
    16             int bound = len - l;
    17             for (int i = 0; i <= bound; i++) {
    18                 int j = i + l - 1;
    19                 isValid[i][j] = (isValid[i + 1][j - 1] && s.charAt(i) == '(' && s.charAt(j) == ')') 
    20                         || (isValid[i][j - 2] && s.charAt(j - 1) == '(' && s.charAt(j) == ')') 
    21                         || (isValid[i + 2][j] && s.charAt(i) == '(' && s.charAt(i + 1) == ')');
    22                 if (isValid[i][j] && l > result) {
    23                     result = l;
    24                 }
    25             }
    26         }
    27         return result;
    28     }
    29 }

    于是忽然意识到这题既然是求substring而不是subsequence,没准可以不用DP来做,因为substring的话感觉好像并不会有很多overlapping的subproblem,而是可以不断地明确砍掉已经处理过的substring进而缩小问题范围,于是想到了依然采用类似Valid Parentheses的计数的方法,用O(N)的时间复杂度和O(1)的空间复杂度就可以解决,代码如下:

     1 public class Solution {
     2     public int longestValidParentheses(String s) {
     3         if (s.length() < 2) {
     4             return 0;
     5         }
     6         int result = 0;
     7         int count = 0;
     8         int diff = 0;
     9         for (int i = 0; i < s.length(); i++) {
    10             if (s.charAt(i) == '(') {
    11                 count++;
    12                 diff++;
    13             } else {
    14                 diff--;
    15                 if (diff < 0) {
    16                     diff = 0;
    17                     count = 0;
    18                 } else if (diff == 0 && result < (count << 1)) {
    19                     result = count << 1;
    20                 }
    21             }
    22         }
    23         count = 0;
    24         diff = 0;
    25         for (int i = s.length() - 1; i >= 0; i--) {
    26             if (s.charAt(i) == ')') {
    27                 count++;
    28                 diff++;
    29             } else {
    30                 diff--;
    31                 if (diff < 0) {
    32                     diff = 0;
    33                     count = 0;
    34                 } else if (diff == 0 && result < (count << 1)) {
    35                     result = count << 1;
    36                 }
    37             }
    38         }
    39         return result;
    40     }
    41 }

    其中用右移一位运算代替了乘2,纯属个人爱好,可能不是一个好的代码习惯。这个代买的思路是先从左向右走一次,每当发现所有在考虑的左右括号完全匹配(即diff == 0时)尝试更新result。第一次走下来如果左括号一直多于右括号的话就无法得到答案,所以再从右到左走一次,这样两次当中可以确保至少有一次能够使得diff == 0,以取得正确答案。

    这一版本的答案是由于之前一直在思考DP的做法而产生的,如果向Valid Parentheses的解法靠拢尝试使用stack的话应该会有使用额外空间但是只需要扫一次的解法。

    下面是重写的code ganker的解法(http://blog.csdn.net/linhuanmars/article/details/20439613),思路主要是:类似Valid Parentheses,用一个stack按顺序记录'('的index,再用一个变量记录当前可能的substring的开头。每当遇到一个')'时,若stack非空,则pop出一个元素,若pop之后非空,说明当前只能匹配到上一个尚未被匹配的'(',即stack.peek();若stack为空,说明可以一直匹配到start。当发现')'多于'('时,即在遇到')'时stack为空,则需要移动start到至少最后一个')'的下一位,因为易得当')'多于'('时,不可能再继续append到之前的任何substring得到依然valid的,所以可以砍掉前面的东西,缩小需要考虑的范围。代码如下:

     1 public class Solution {
     2     public int longestValidParentheses(String s) {
     3         Stack<Integer> stack = new Stack<>();
     4         int result = 0;
     5         int start = 0;
     6         for (int i = 0; i < s.length(); i++) {
     7             if (s.charAt(i) == '(') {
     8                 stack.push(i);
     9             } else {
    10                 if (!stack.isEmpty()) {
    11                     stack.pop();
    12                     result = stack.isEmpty() ? Math.max(result, i - start + 1) : Math.max(result, i - stack.peek());
    13                 } else {
    14                     start = i + 1;
    15                 }
    16             }
    17         }
    18         
    19         return result;
    20     }
    21 }

    这个解法的关键insight在于理解用stack存index的真正目的是记录可能的substring左边界,用于在找到一个')'判断左边界应该在哪,值得集中练习掌握,LeetCode上面相关的题目还有上面提到的Largest Rectangle in Histogram,Trapping Rain Water等。

  • 相关阅读:
    校验身份证有效性
    JAVA实现redis超时失效key 的监听触发
    Java8中时间日期库的20个常用使用示例
    ppt制作元素采集
    查找数据的网站
    在centos7中python3的安装注意
    使用yum安装不知道到底安装在什么文件夹
    linux为什么不可以添加硬链接
    五一之起一台服务器玩玩-花生壳配置
    centos6.5-vsftp搭建
  • 原文地址:https://www.cnblogs.com/icecreamdeqinw/p/4329024.html
Copyright © 2011-2022 走看看