IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> LeetCode_String_301. Remove Invalid Parentheses 删除无效的括号【DFS、剪枝】【Java】【困难】 -> 正文阅读

[数据结构与算法]LeetCode_String_301. Remove Invalid Parentheses 删除无效的括号【DFS、剪枝】【Java】【困难】

目录

一,题目描述

英文描述

中文描述

示例与说明

二,解题思路

三,AC代码

Java

DFS

DFS+剪枝

四,解题过程

第一搏

第二搏

第三搏


一,题目描述

英文描述

Given a string s that contains parentheses and letters, remove the minimum number of invalid parentheses to make the input string valid.

Return all the possible results. You may return the answer in any order.

中文描述

给你一个由若干括号和字母组成的字符串?s?,删除最小数量的无效括号,使得输入的字符串有效。

返回所有可能的结果。答案可以按?任意顺序?返回。

示例与说明

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/remove-invalid-parentheses
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

二,解题思路

参考三叶大佬的思路@宫水三叶【【宫水三叶】将括号的「是否合法」转化为「数学判定」】

目标字符串的形成过程中,对每个字符都有 不取 两种选择,因此可以考虑dfs递归的方法暴力遍历每种情况。

为方便计算,引入几个变量:

  • max记录最多可以匹配的括号对数,max取值为左右括号数目中较小的那个;
  • 用score记录左右括号匹配情况,左括号score++,右括号score--。score < 0 表明左括号数目不足以匹配右括号,score > max 表明左括号数目超出可以匹配的最多括号对数;
  • lDel和rDel分别记录需要删除的左右括号数目;
  • maxLen字符串最大长度 = 原字符串长度 - lDel - rDel;

递归的出口条件:

  1. 左括号数目不足以匹配右括号数目。score < 0;
  2. 左括号数目大于可以匹配的最多括号对数(比如左括号有5个,右括号有3个,最多只能匹配3对括号)。score? > max;
  3. 需要删除的左、右括号数目小于0。lDel < 0 || rDel < 0;
  4. 已经遍历字符串中所有字符。index = s.length();

当需要删除的左、右括号数目为0,并且当前字符串长度为字符串最大长度maxLen时,将其添加到结果集中即可。

三,AC代码

Java

DFS

class Solution {
    int l, r, max, maxLen;// 分别记录左括号数目、右括号数目、最多可以匹配的括号对数、最长子串长度
    Set<String> ans = new HashSet<>();

    public List<String> removeInvalidParentheses(String s) {
        for (int i = 0; i < s.length(); i++) {
            if (s.charAt(i) == '(') l++;
            else if (s.charAt(i) == ')') r++;
        }
        max = Math.min(l, r);// 最多可以匹配的括号对数
        dfs(0, "", s, 0);
        return new ArrayList<String>(ans);
    }
    
    // index记录当前遍历的下标、tem为当前形成的字符串、s为原字符串、score为得分(左括号加一分,右括号减一分)
    public void dfs(int index, String tem, String s, int score) {
        if (score < 0 || score > max) return;
        if (index == s.length()) {// !!!遍历完所有字符
            if (score == 0 && tem.length() >= maxLen) {
                if (tem.length() > maxLen) {
                    ans.clear();
                    maxLen = tem.length();
                }
                ans.add(tem);
            }
            return;// !!!这里也是出口条件之一
        }
        char c = s.charAt(index);
        if (c == '(') {
            dfs(index + 1, tem + '(', s, score + 1);
            dfs(index + 1, tem, s, score);
        } else if (c == ')') {
            dfs(index + 1, tem + ')', s, score - 1);
            dfs(index + 1, tem, s, score);
        } else {
            dfs(index + 1, tem + c, s, score);
        }
    }

}

DFS+剪枝

class Solution {
    int max, maxLen;// 分别记录最多可以匹配的括号对数、最长子串长度
    Set<String> ans = new HashSet<>();

    public List<String> removeInvalidParentheses(String s) {
        int l = 0, r = 0;// 记录左右括号数目
        int lDel = 0, rDel = 0;// lDel需要删除的左括号数目,rDel需要删除的右括号数目
        for (int i = 0; i < s.length(); i++) {
            if (s.charAt(i) == '(') {
                l++;
                lDel++;
            }
            else if (s.charAt(i) == ')') {
                r++;
                if (lDel > 0) lDel--;
                else rDel++;
            }
        }
        max = Math.min(l, r);// 最多可以匹配的括号对数
        maxLen = s.length() - lDel - rDel;// 最长子串长度:总长度-需要删除的左右括号数
        dfs(0, "", s, 0, lDel, rDel);
        return new ArrayList<String>(ans);
    }

    // index记录当前遍历的下标、tem为当前形成的字符串、s为原字符串、score为得分(左括号加一分,右括号减一分)、lDel需要删除的左括号数目、rDel需要删除的右括号数目
    public void dfs(int index, String tem, String s, int score, int lDel, int rDel) {
        if (score < 0 || score > max || lDel < 0 || rDel < 0) return;// 出口:不符合条件
        if (lDel == 0 && rDel == 0 && tem.length() == maxLen) {
            ans.add(tem);
        }
        if (index == s.length()) {// !!!遍历完所有字符
            return;// !!!这里也是出口条件之一
        }
        char c = s.charAt(index);
        if (c == '(') {
            dfs(index + 1, tem + '(', s, score + 1, lDel, rDel);
            dfs(index + 1, tem, s, score, lDel - 1, rDel);// 不添加当前字符,需要删除的左括号数目减一
        } else if (c == ')') {
            dfs(index + 1, tem + ')', s, score - 1, lDel, rDel);
            dfs(index + 1, tem, s, score, lDel, rDel - 1);// 不添加当前字符,需要删除的右括号数目减一
        } else {
            dfs(index + 1, tem + c, s, score, lDel, rDel);
        }
    }

}

四,解题过程

第一搏

一开始想法很简单,先遍历一遍字符串,看看多出来多少个右括号,并记录他们的位置

以这些位置作为最后要删除的右括号,依次尝试删除之前出现的右括号,并将结果存入set中,举个例子:s = "()())()"

可以看出最晚 i=4 时,必须要删除一个右括号,删除之前的任何一个右括号都可以使得最终的字符串括号有效。

同理,最少需要删除的括号数目就是多出来的右括号数目,而且之前遍历时已经获取了他们最晚删除的位置。

class Solution {
    public List<String> removeInvalidParentheses(String s) {
        int left = 0;// 记录左括号数目
        List<Integer> rightDeletePos = new ArrayList<>();// 记录需要删除的最右侧右括号位置
        List<Integer> posToDelete = new ArrayList<>();
        Set<String> record = new HashSet<>();// 记录遍历所有的可能解并去重
        List<String> ans = new ArrayList<>();

        if (s.charAt(0) == ')') return ans;// !!! [")("]

        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if (c == '(') left++;
            else if (c == ')') {
                if (left > 0) left--;
                else rightDeletePos.add(i);
            }
        }

        if (rightDeletePos.size() == 0) return ans;

        getAns(record, s, rightDeletePos, posToDelete);
        return new ArrayList<String>(record);


    }
    public void getAns(Set<String> record, String s, List<Integer> rightDeletePos, List<Integer> posToDelete) {
        if (rightDeletePos.size() == posToDelete.size()) {
            record.add(removeChars(s, posToDelete));
            return;
        }
        for (int pos : rightDeletePos) {
            for (int i = 0; i <= pos; i++) {
                if (!posToDelete.contains(i) && s.charAt(i) == ')') {
                    posToDelete.add(i);
                    getAns(record, s, rightDeletePos, posToDelete);
                    posToDelete.remove(posToDelete.size() - 1);
                }
            }
        }

    }
    public String removeChars(String s, List<Integer> posToDelete) {
        StringBuilder sb = new StringBuilder(s);
        for (int i = 0; i < posToDelete.size(); i++) {
            int pos = posToDelete.get(i) - i;// 每次删除字符后,字符串长度减一,对应后面记录的位置需要减一
            sb.deleteCharAt(pos);
        }
        return new String(sb);
    }
}

心细的同学可能已经发现了,上面的思路中有个非常明显的缺陷:只考虑了括号数目多于括号的情况

  • 左括号数目大于右括号数目:处理流程和上面的思路类似,不过需要从右向左遍历字符串,记录多出来的左括号数目位置,然后进一步处理;
  • 左括号数目等于右括号数目:类似于这样s="))()()((",依次执行上面两种情况的处理方法即可,先删除多余右括号,再删除多余左括号;

以上问题大概就可以解决了(因为没亲手实现出来),可以看出思路勉强还可以,但是实现过程较为复杂和繁琐

第二搏

目标字符串的形成过程中,对每个字符都有 不取 两种选择,因此可以考虑dfs递归的方法暴力遍历每种情况。

递归的出口条件:

  • 1,左括号数目不足以匹配右括号数目;
  • 2,左括号数目大于可以匹配的最多括号对数(比如左括号有5个,右括号有3个,最多只能匹配3对括号);
  • 3,已经遍历字符串中所有字符;

其中遍历所有字符后,可以对当前形成的字符串进行校验,考虑是否加入结果集(左右括号数目完全匹配)、清空并更新结果集(当前字符串长度大于结果集中字符串长度,需要删除的字符更少,优先选择)

class Solution {
    int l, r, max, maxLen;// 分别记录左括号数目、右括号数目、最多可以匹配的括号对数、最长子串长度
    Set<String> ans = new HashSet<>();

    public List<String> removeInvalidParentheses(String s) {
        for (int i = 0; i < s.length(); i++) {
            if (s.charAt(i) == '(') l++;
            else if (s.charAt(i) == ')') r++;
        }
        max = Math.min(l, r);// 最多可以匹配的括号对数
        dfs(0, "", s, 0);
        return new ArrayList<String>(ans);
    }
    
    // index记录当前遍历的下标、tem为当前形成的字符串、s为原字符串、score为得分(左括号加一分,右括号减一分)
    public void dfs(int index, String tem, String s, int score) {
        if (score < 0 || score > max) return;
        if (index == s.length()) {// !!!遍历完所有字符
            if (score == 0 && tem.length() >= maxLen) {
                if (tem.length() > maxLen) {
                    ans.clear();
                    maxLen = tem.length();
                }
                ans.add(tem);
            }
            return;// !!!这里也是出口条件之一
        }
        char c = s.charAt(index);
        if (c == '(') {
            dfs(index + 1, tem + '(', s, score + 1);
            dfs(index + 1, tem, s, score);
        } else if (c == ')') {
            dfs(index + 1, tem + ')', s, score - 1);
            dfs(index + 1, tem, s, score);
        } else {
            dfs(index + 1, tem + c, s, score);
        }
    }

}

第三搏

在遍历原字符串时,其实可以得到需要删除的左、右括号数目,根据这个条件可以在递归过程中避免部分不必要的递归分支

可以看出剪枝的效果非常明显!*★,°*:.☆( ̄▽ ̄)/$:*.°★* 。

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2022-05-14 10:09:04  更:2022-05-14 10:09:53 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 1:26:27-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码