栈与括号匹配——20、636、591、32(简中难难)

20. 有效的括号(简单)

给定一个只包括 '('')''{''}''['']' 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

  1. 左括号必须用相同类型的右括号闭合。
  2. 左括号必须以正确的顺序闭合。
  3. 每个右括号都有一个对应的相同类型的左括号。

解法一、栈

注意边界判断,主要是栈是否为空的讨论

class Solution {
    public boolean isValid(String s) {
        Deque<String> stack = new ArrayDeque<>();
        int len = s.length();
        for(int i = 0;i < len;i++){
            switch (s.charAt(i)){
                case '(','{','[':
                    stack.push(String.valueOf(s.charAt(i)));
                    break;
                case ')':
                    if(!stack.isEmpty() && stack.peek().charAt(0) == '(')stack.pop();
                    else return false;
                    break;
                case '}':
                    if(!stack.isEmpty() && stack.peek().charAt(0) == '{')stack.pop();
                    else return false;
                    break;
                case ']':
                    if(!stack.isEmpty() && stack.peek().charAt(0) == '[')stack.pop();
                    else return false;
                    break;

            }
        }
        if(stack.isEmpty())return true;
        else return false;
    }
}

636. 函数的独占时间(中等)

有一个 单线程 CPU 正在运行一个含有 n 道函数的程序。每道函数都有一个位于  0 和 n-1 之间的唯一标识符。

函数调用 存储在一个 调用栈 上 :当一个函数调用开始时,它的标识符将会推入栈中。而当一个函数调用结束时,它的标识符将会从栈中弹出。标识符位于栈顶的函数是 当前正在执行的函数 。每当一个函数开始或者结束时,将会记录一条日志,包括函数标识符、是开始还是结束、以及相应的时间戳。

给你一个由日志组成的列表 logs ,其中 logs[i] 表示第 i 条日志消息,该消息是一个按 "{function_id}:{"start" | "end"}:{timestamp}" 进行格式化的字符串。例如,"0:start:3" 意味着标识符为 0 的函数调用在时间戳 3 的 起始开始执行 ;而 "1:end:2" 意味着标识符为 1 的函数调用在时间戳 2 的 末尾结束执行。注意,函数可以 调用多次,可能存在递归调用 

函数的 独占时间 定义是在这个函数在程序所有函数调用中执行时间的总和,调用其他函数花费的时间不算该函数的独占时间。例如,如果一个函数被调用两次,一次调用执行 2 单位时间,另一次调用执行 1 单位时间,那么该函数的 独占时间 为 2 + 1 = 3 。

以数组形式返回每个函数的 独占时间 ,其中第 i 个下标对应的值表示标识符 i 的函数的独占时间。

解法一、栈模拟

st当作调用栈。因为start和end的长短不同,分为两个处理逻辑,如果是start,处理切断部分,更新调用栈和时间戳;如果是end,处理切断部分,弹出正在调用的函数。如果此时栈是空的,则把时间戳置为-1,意为不必处理切断部分;如果不是空的,则更新时间戳和id,处理连续两个或多个结束消息的情况。

在官方那里学到了以下写法,确实比我用的这种判断方式简洁

 int idx = Integer.parseInt(log.substring(0, log.indexOf(':')));
 String type = log.substring(log.indexOf(':') + 1, log.lastIndexOf(':'));
 int timestamp = Integer.parseInt(log.substring(log.lastIndexOf(':') + 1));

或者

String[] tag = log.split(":");//分割
int id = Integer.parseInt(tag[0]);
String se= tag[1];
int time = Integer.parseInt(tag[2]); 

class Solution {
    public static int[] exclusiveTime(int n, List<String> logs) {
        Deque<Integer> st = new ArrayDeque<>();
        int [] res = new int[n];
        int id = -1,timeStamp = -1;
        for(String s:logs){
            int startIndex ,endIndex;
            startIndex = s.indexOf("start");
            endIndex = s.indexOf("end");
            if(startIndex != -1){//开始
                if(timeStamp != -1)res[id] += Integer.parseInt(s.substring(startIndex+6)) - timeStamp;
                id = Integer.parseInt(s.substring(0,startIndex-1));
                timeStamp = Integer.parseInt(s.substring(startIndex+6));
                st.push(id);
            }else{//结束
                if(timeStamp!=-1)res[id] += Integer.parseInt(s.substring(endIndex+4)) - timeStamp+1;
                st.pop();
                if(!st.isEmpty()) {
                    id = st.peek();
                    timeStamp = Integer.parseInt(s.substring(endIndex + 4))+1;
                }else{
                    timeStamp = -1;
                }
            }
        }
        return res;
    }
}

591. 标签验证器(困难)

给定一个表示代码片段的字符串,你需要实现一个验证器来解析这段代码,并返回它是否合法。合法的代码片段需要遵守以下的所有规则:

  1. 代码必须被合法的闭合标签包围。否则,代码是无效的。
  2. 闭合标签(不一定合法)要严格符合格式:<TAG_NAME>TAG_CONTENT</TAG_NAME>。其中,<TAG_NAME>是起始标签,</TAG_NAME>是结束标签。起始和结束标签中的 TAG_NAME 应当相同。当且仅当 TAG_NAME 和 TAG_CONTENT 都是合法的,闭合标签才是合法的
  3. 合法的 TAG_NAME 仅含有大写字母,长度在范围 [1,9] 之间。否则,该 TAG_NAME 是不合法的
  4. 合法的 TAG_CONTENT 可以包含其他合法的闭合标签cdata (请参考规则7)和任意字符(注意参考规则1)除了不匹配的<、不匹配的起始和结束标签、不匹配的或带有不合法 TAG_NAME 的闭合标签。否则,TAG_CONTENT 是不合法的
  5. 一个起始标签,如果没有具有相同 TAG_NAME 的结束标签与之匹配,是不合法的。反之亦然。不过,你也需要考虑标签嵌套的问题。
  6. 一个<,如果你找不到一个后续的>与之匹配,是不合法的。并且当你找到一个<</时,所有直到下一个>的前的字符,都应当被解析为 TAG_NAME(不一定合法)。
  7. cdata 有如下格式:<![CDATA[CDATA_CONTENT]]>CDATA_CONTENT 的范围被定义成 <![CDATA[ 和后续的第一个 ]]>之间的字符。
  8. CDATA_CONTENT 可以包含任意字符。cdata 的功能是阻止验证器解析CDATA_CONTENT,所以即使其中有一些字符可以被解析为标签(无论合法还是不合法),也应该将它们视为常规字符。 

解法一、栈+分类+模拟+正则

 Cdata和tagName合法性用正则。而对函数主体,①若是对于一个<,判断后续字母,分下面四种情况;②对于除<以外的字母,则判断此时栈中是否有东西(TagName),这代表它是被包裹的。如果空栈,则false。

  1. <!,则是Cdata
  2. </,闭合符
  3. <+字母,起始符
  4. 不合法

一个易错点是<A></A><B></B>,最初以为是因为context中没有东西,最后发现是因为违反了规则1,两个代码没被合法的标签圈住。所以,对于起始符,也要加额外的判断,如果起始符不在最开头、栈也是空的,则false

不太清楚这里的判断花费时间在哪,猜测应该是s.matches函数,匹配正则较慢。但感觉这样的可读性会强很多,所以不改了

class Solution {
    public static boolean isValid(String code) {
        Deque<String> st = new ArrayDeque<>();
        int len = code.length();
        for(int i = 0;i < len;i++){
            int j = -1;
            if(code.charAt(i) == '<'){
                if(i < len-1 && code.charAt(i+1) == '!'){//cdate
                    j = code.indexOf("]]>",i);
                    if(j == -1 || j+2 >=len || !isCdataValid(code.substring(i,j+3))||st.isEmpty())return false;
                    i = j + 2;
                }else if(i < len-1 && code.charAt(i+1) == '/'){
                    j = code.indexOf(">",i);
                    if(j == -1 || !isTagNameValid(code.substring(i+2,j)))return false;
                    if(code.substring(i+2,j).equals(st.peek()))st.pop();
                    else return false;
                    i = j;
                }else if(i < len-1 && Character.isLetter(code.charAt(i+1))){
                    j = code.indexOf(">",i);
                    if(j == -1 || !isTagNameValid(code.substring(i+1,j))|| (i!=0 && st.isEmpty()))return false;
                    st.push(code.substring(i+1,j));
                    i = j;
                }else{
                    return false;
                }
            }else {
                if(st.isEmpty())return false;
            }
        }
        return st.isEmpty();
    }
    private static boolean isTagNameValid(String code) {
        int len = code.length();
        return len>0 && len <=9 && code.matches("[A-Z]+");
    }
    private static boolean isCdataValid(String code) {
        return code.matches("<!\\[CDATA\\[(.*?)]]>");
    }
}

32. 最长有效括号(困难)

解法一、dp

class Solution {
    public int longestValidParentheses(String s) {
        int maxans = 0;
        int[] dp = new int[s.length()];
        for (int 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;
                }
                maxans = Math.max(maxans, dp[i]);
            }
        }
        return maxans;
    }
}

解法二、栈

若是左括号,则放进下标。若是右括号,则弹出,st不空则求一下res,st空了则放入无匹配的右括号下标。

这个方法也可以用常量来模拟,用一个count当作计数器,用一个index当作最后一个无匹配右括号下标。count的正负即left和right。见解法三

class Solution {
    public int longestValidParentheses(String s) {
        int res = 0;
        Deque<Integer>st = new ArrayDeque<>();
        st.push(-1);
        int len = s.length();
        for(int i = 0;i < len;i++){
            if(s.charAt(i) == '('){
                st.push(i);
            }else{
                st.pop();
                if(!st.isEmpty()){
                    res = Math.max(i-st.peek(),res);
                }else{
                    st.push(i);
                }
            }
        }
        return res;
    }
}

解法三、不使用额外空间

在此方法中,我们利用两个计数器 left 和 right 。首先,我们从左到右遍历字符串,对于遇到的每个 ‘(’,我们增加 left 计数器,对于遇到的每个 ‘)’ ,我们增加 right 计数器。每当 left 计数器与 right 计数器相等时,我们计算当前有效字符串的长度,并且记录目前为止找到的最长子字符串。当 right 计数器比 left 计数器大时,我们将 left 和 right 计数器同时变回 0。

这样的做法贪心地考虑了以当前字符下标结尾的有效括号长度,每次当右括号数量多于左括号数量的时候之前的字符我们都扔掉不再考虑,重新从下一个字符开始计算,但这样会漏掉一种情况,就是遍历的时候左括号的数量始终大于右括号的数量,即 (() ,这种时候最长有效括号是求不出来的。

解决的方法也很简单,我们只需要从右往左遍历用类似的方法计算即可,只是这个时候判断条件反了过来:

当 left 计数器比 right 计数器大时,我们将 left 和 right 计数器同时变回 0
当 left 计数器与 right 计数器相等时,我们计算当前有效字符串的长度,并且记录目前为止找到的最长子字符串
这样我们就能涵盖所有情况从而求解出答案。

作者:力扣官方题解
链接:https://leetcode.cn/problems/longest-valid-parentheses/solutions/314683/zui-chang-you-xiao-gua-hao-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

class Solution {
    public int longestValidParentheses(String s) {
        int left = 0, right = 0, maxlength = 0;
        for (int 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 (int 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;
    }
}

作者:力扣官方题解
链接:https://leetcode.cn/problems/longest-valid-parentheses/solutions/314683/zui-chang-you-xiao-gua-hao-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

解法四、括号匹配

分享一个超简单思路,只会括号匹配就行。
题目让找到括号匹配成功的最长连续字符,我们可以进行问题转化。这里有两个关键点。1.匹配成功,2.最长连续。

因此,分别模拟以上两个过程即可 首先是“匹配成功”,这个用栈来实现。为了给第2步做准备,我们要在匹配成功时做个记号,这里开辟一个数组,匹配成功时,在 '(' 和 ")' 配对成功的索引位置处记为1。

然后统计数组里面连续1的个数,最长的那个就是结果。

. - 力扣(LeetCode) 


碎碎念

  • 20就是熟悉栈结构。636感觉有点模拟单核下进程的调用。。。需要判断每一段的开头结尾。这个如果会indexOf那一对函数、subString、Integer.parseInt,判断就会变得简单很多。591和32都很考察分类讨论,总体思路的清晰性
  • 重新学习了dp,熟悉了Deque模拟Stack的方法,熟悉了一点正则
  • 刚回来果然节奏很慢。连for-each循环都不知道怎么写了,搜了半天
  • 总用时2h45mins
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值