• 算法——括号问题及其衍生


    求最长合法括号序列问题

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

    解题思路:

    1. 利用一个前缀和的思想,顺序遍历,遇到左括号和加一;遇到右括号,和减一。当和为0的时候,就是一个合法序列。如果和为负数,那么以当前起点产生的序列不再可能产生合法的序列,因此,将起点设置到当前点的下一个位置,并将和置零重新累加。需要注意的是,如果只从一个方向遍历,会漏掉有效括号在在中间的情况,因此,在逆序遍历一遍就好了,同时,条件也要相反。
    class Solution {
        public int longestValidParentheses(String s) {
            int res = 0, sum = 0;
            for(int i = 0, j = 0; i < s.length(); i++) {
                if(s.charAt(i) == '(') {
                    sum ++;
                } else {
                    sum --;
                }
    
                if(sum == 0) {
                    res = Math.max(res, i - j + 1);
                } else if(sum < 0) {
                    j = i + 1;
                    sum = 0;
                }
            }
            
            sum = 0;
            for(int i = s.length() - 1, j = s.length() - 1; i >= 0 ; i--) {
                if(s.charAt(i) == ')') {
                    sum ++;
                } else {
                    sum --;
                }
    
                if(sum == 0) {
                    res = Math.max(res, j - i + 1);
                } else if(sum < 0) {
                    j = i - 1;
                    sum = 0;
                }
            }
    
            return res;
        }
    }
    

    删除无效的括号

    删除最小数量的无效括号,使得输入的字符串有效,返回所有可能的结果。
    说明: 输入可能包含了除 ( 和 ) 以外的字符。
    leetcode

    解题思路:因为要求最少的删除数,所以,一定是要删除括号中多的那一类,或者是次序混乱的部分。

    1. 首先通过一次遍历,判断需要删除的左右括号的数量。如果左括号多的数量为0,出现了右括号那么这个右括号就是该删掉的,如果出现了左括号,那先记着这个左括号是改删的,待会儿出现了右括号于其配对,那么该删的左括号减一。
    2. 然后进行dfs,从一个空低字符串开始添加。
      1. 如果遇到不是括号的字符,就直接添加进去;
      2. 如果遇到左括号,那么就判断左括号是不是需要删除,如果需要删除,就增加遍历索引,同时新构造的字符串中不添加这个左括号。
      3. 右括号相似处理。
    3. 直到遍历完这个字符串,查看左右括号数是否相等,如果相等,就是一个合法的串。
    4. 这里利用到了HashSet,因为再遍历连续相同的括号的时候,会产生一样的结果。
    class Solution {
        HashSet<String> set;
        public List<String> removeInvalidParentheses(String s) {
            List<String> res = new ArrayList<>();
            set = new HashSet<>();
    
            int r = 0, l = 0;
    
    		// 统计该删除的左右括号的数量
            for(char c : s.toCharArray()) {
                if(c == '(') l ++;
                else if(c == ')'){
                    if(l == 0) r ++;
                    else {
                        l --;
                    }
                }
            }
    
            dfs(s, 0, "", 0, l, r);
    
            for(String ss : set) res.add(ss);
    
            return res;
        }
    
    	// s: 就是这个需要遍历的原始字符串
    	// u:遍历字符串的时候需要使用的索引
    	// path:一种结果
    	// cnt:统计左右括号的和
    	// l:需要删除的左括号的数量
    	// r:需要删除的右括号的数量
        public void dfs(String s, int u, String path, int cnt, int l, int r) {
        	// 如果括号和小于零,那就是一个非法序列
            if(cnt < 0) return;
            if(s.length() == u) {
            	// 遍历到了最后 如果括号和为0,说明是个合法序列
                if(cnt == 0) set.add(path);
                return;
            }
    		
    		// 如果不是括号,就直接添加到当前序列后面
            if(s.charAt(u) != '(' && s.charAt(u) != ')') dfs(s, u + 1, path + s.charAt(u), cnt, l, r);
            else {
                if(s.charAt(u) == '(') {
                	// 如果有需要删除的左括号,那就忽略这个括号,同时需要删除的左括号数量减一
                    if(l > 0) dfs(s, u + 1, path, cnt, l - 1, r);
                    // 先不删这个左括号,括号和加一
                    dfs(s, u + 1, path + '(', cnt + 1, l, r);
                } else {
                	// 同上
                    if(r > 0) dfs(s, u + 1, path, cnt, l, r - 1);
                    dfs(s, u + 1, path + ')', cnt - 1, l, r);
                }
            } 
        }
    }
    

    下面这个是改进版本,不太好理解。
    就是将连续相同的括号进行特殊处理。

    class Solution {
        List<String> res;
        public List<String> removeInvalidParentheses(String s) {
            res = new ArrayList<>();
    
            int r = 0, l = 0;
    
            for(char c : s.toCharArray()) {
                if(c == '(') l ++;
                else if(c == ')'){
                    if(l == 0) r ++;
                    else {
                        l --;
                    }
                }
            }
    
            dfs(s, 0, "", 0, l, r);
    
            return res;
        }
    
        public void dfs(String s, int u, String path, int cnt, int l, int r) {
            if(s.length() == u) {
                if(cnt == 0) res.add(path);
                return;
            }
    
            if(s.charAt(u) != '(' && s.charAt(u) != ')') dfs(s, u + 1, path + s.charAt(u), cnt, l, r);
            else {
                if(s.charAt(u) == '(') {
                    int k = u;
                    while(k < s.length() && s.charAt(k) == '(') k++;
                    l -= k - u;
                    for(int i = k - u; i >= 0; i--) {
                        if(l >= 0) dfs(s, k, path, cnt, l, r);
                        path += '(';
                        cnt ++;
                        l ++;
                    }
                } else {
                    int k = u;
                    while(k < s.length() && s.charAt(k) == ')') k++;
                    r -= k - u;
                    for(int i = k - u; i >= 0; i--) {
                        if(cnt >= 0 && r >= 0) dfs(s, k, path, cnt, l, r);
                        path += ')';
                        cnt --;
                        r ++;
                    }
                }
            } 
        }
    }
    

    衍生问题

    最长匹配对

    给定一个放有字符和数字的数组,找到最长的子数组,且包含的字符和数字的个数相同。
    返回该子数组,若存在多个最长子数组,返回左端点最小的。若不存在这样的数组,返回一个空数组。
    leetcode

    解题思路:

    • 同样利用前缀和的思想,遇到字符和加一,遇到数字和减一,正确序列的和一定是0。如果只是利用前缀和,需要两层循环,枚举所有可能的组合才行,这样时间是n方的。

    • 这里,我们可以借助哈希表,对前面已经运算过的前缀和进行存储,同时对于相同的前缀和,只记录最左边的下标。当遇到相同的前缀和,那么这两个下标之间就是一个正确值,因为和的差为0,说明这个范围内的的数字和字符数量是一样的,是一个合法的答案。

    • 不同于括号问题,这里的字符和数字的前后顺序没有关系,只需要数量匹配即可。

    class Solution {
        public String[] findLongestSubarray(String[] array) {
            int n = array.length;
            int sum = 0;
            Map<Integer, Integer> map = new HashMap<>();
            int x = 0, y = 0;
            map.put(0, -1);
    
            for(int i = 0; i < n; i++) {
                char cur = array[i].charAt(0);
                if(Character.isDigit(cur)) {
                    sum++;
                } else {
                    sum--;
                }
    
                if(!map.containsKey(sum)) map.put(sum, i);
                else {
                    if(x - y < i - map.get(sum)){
                        x = i + 1;
                        y = map.get(sum) + 1;
                    }
                }
            }
    
            return Arrays.copyOfRange(array, y, x);
        }
    }
    
  • 相关阅读:
    ASP.NET编程中的十大技巧
    ArcIMS初级教程(2)
    网站的CMS建站程序 yi
    买车后的开销 yi
    谈谈我的梦之一~~大数据时代电子商务
    【ActiveMQ Tuning】vmCursor on Destination
    【ActiveMQ Tuning】JMS Transactions
    【ActiveMQ Tuning】System Environment
    【ActiveMQ Tuning】Threading Optimizations
    【ActiveMQ Tuning】Prefetch Limit
  • 原文地址:https://www.cnblogs.com/lippon/p/14117636.html
Copyright © 2020-2023  润新知