算法训练营day11

一、有效的括号

如果是左括号就压栈(push),匹配到右括号时出栈

  1. 若数量为奇数或数量 <= 1,匹配失败
  2. 若第一个字符不是左括号,匹配失败
  3. 若匹配到右括号时,栈内没有元素(没有左括号)匹配失败
  4. 若元素遍历完,栈内非空则匹配失败(左右括号数量不相等)
  5. 利用HashMap创建左右括号的对应关系 并且 添加 ?:? (纯技巧)巧妙解决问题
class Solution {
        //使用Map来记录左右括号对应关系
    private static final Map<Character,Character> map = new HashMap<Character,Character>(){{
        //这里的 ? ? 是为了解决边界问题,当栈为空遇到又遇到右括号时,? 对应 右括号自然返回false
        put('{','}'); put('[',']'); put('(',')'); put('?','?');
    }};
    public boolean isValid(String s) {
        //括号数量为奇数,或只有一个或零个括号,返回false
        if(s.length() % 2 != 0 || s.length() <= 1){
            return false;
        }
        //利用栈来进行计算,如果是左括号就压栈,出现有括号就弹栈,判断左右括号是否匹配
        //不匹配返回false,匹配继续遍历

        //如果第一个字符不在map的key集合中,也就不是左括号 '{', '[', '(',那么直接返回false。匹配第一个字符必须是左括号,例, }{ ,肯定匹配失败
        if(!map.containsKey(s.charAt(0))) return false;
        //添加一个 ? 在栈中,防止空栈.removeLast()报错的情况
        LinkedList<Character> stack = new LinkedList<Character>(){{add('?');}};
        for(Character c:s.toCharArray()){
            //左括号压栈内(push)
            if(map.containsKey(c)) stack.addLast(c);
            //右括号判断出栈的左括号的value是否匹配,不匹配也返回false
            else if(map.get(stack.removeLast()) != c) return false;
        }
        //如果最后stack剩余大小为1(即add('?')),代表所有括号都匹配上了,1是true
        return stack.size() == 1;
    }
}
为什么采用HashMap的解法?

考虑到这只是三种括号的情况,如果是更多种情况匹配,代码维护非常麻烦,而这种代码在添加多种情况时只需在HashMap中添加对应key-value即可,拓展性比较强

二、删除字符串中所有相邻重复项

参考Carl哥的代码

涉及到字符串频繁变化时,要联想到 StringBuilder || StringBuffer

class Solution {
    public String removeDuplicates(String s) {
//ArrayDeque比LinkedList在除了删除元素之外会快一点
//Answer最高赞的最后一句话,The only better operation of a linked list is removing the current element during iteration.
//参考:https://stackoverflow.com/questions/6163166/why-is-LinkedList-better-than-linkedlist
        ArrayDeque<Character> deque = new ArrayDeque<>();
        char ch;
        for (int i = 0; i < s.length(); i++) {
            ch = s.charAt(i);
            //如果栈为空或者头部字符不等于当前字符,则压栈
            if (deque.isEmpty() || deque.peek() != ch) {
                deque.push(ch);
            } else {
                //否则(.peek() == ch),则出栈
                deque.pop();
            }
        }
        StringBuilder sb = new StringBuilder();
        while (!deque.isEmpty()) {
            sb.insert(0, deque.pop());
        }
        return sb.toString();
    }
}

效率对比

  1. 使用 + 号进行字符串拼接在这里插入图片描述

  2. 使用StringBuilder在这里插入图片描述

ArrayDeque & LinkedList 的一些区别
  1. ArrayDeque & LinkedList 是Deque的两个实现类
  2. Another difference to bear in mind: LinkedList supports null elements, whereas ArrayDeque does not.(要记住的另一个区别是:LinkedList支持null元素,而ArrayDeque不支持。)
  3. Also another small disadvantage (for real-time applications) is that on a push/add operation it takes a bit more when the internal array of the ArrayDeque is full, as it has to double its size and copy all the data.(另一个小缺点(对于实时应用程序)是,在push/add操作中,当ArrayDeque的内部数组已满时,它需要更多的时间,因为它必须将其大小加倍并复制所有数据。)
  4. If your requirement is storing 100 or 200 elements, it wouldn’t make much of a difference using either of the Queues.(如果您的需求是存储100或200个元素,那么使用这两个队列中的任何一个都不会有太大区别。)
  5. However, if you are developing on mobile, you may want to use an ArrayList or ArrayDeque with a good guess of maximum capacity that the list may be required to be because of strict memory constraint.(然而,如果你是在移动设备上开发,你可能想使用一个“ArrayList”或“ArrayDeque”,并猜测列表的最大容量可能是因为严格的内存约束。) 该条对应第2条 ArrayDeque在使用内存超过设定最大内存时,需要扩张至2倍(double)内存,非常消耗时间,或者因为内存无法扩张到2倍而无法运行
  6. A lot of code exists, written using a LinkedList so tread carefully when deciding to use a ArrayDeque especially because it DOESN’T implement the List interface(I think that’s reason big enough). It may be that your codebase talks to the List interface extensively, most probably and you decide to jump in with an ArrayDeque. Using it for internal implementations might be a good idea… (有很多代码是用“LinkedList”编写的,所以在决定使用“ArrayDeque”时要小心,特别是因为它(ArrayDeque)没有实现“List”'接口(我认为这是足够大的原因)。您的代码库可能与List接口进行广泛的对话,最有可能的是然后你决定加入一个数组Deque。将其用于内部实现可能是一个好主意…)
三、逆波兰表达式求值

逻辑,较易

  1. 遇到数字时,将数字压入栈
  2. 遇到运算符时,将栈顶两个元素弹出来,第一个弹出来的在运算符右侧,第二个在左侧,计算完成后将结果压入栈
class Solution {
    public int evalRPN(String[] tokens) {
        Deque<Integer> stack = new LinkedList<Integer>();
        for (int i = 0; i < tokens.length; i++) {
            String token = tokens[i];
    //第一个点,isNumber,判断是否为数字,如果为数字则压栈,
            if (isNumber(token)) {
	// Integer.parseInt(token),将字符串类型解析为Integer类型方便后面计算
                stack.push(Integer.parseInt(token));
            } else {
                int num2 = stack.pop();
                int num1 = stack.pop();
    //switch(var) case 可应用于对应关系比较少的情景
                switch (token) {
                    case "+":
                        stack.push(num1 + num2);
                        break;
                    case "-":
                        stack.push(num1 - num2);
                        break;
                    case "*":
                        stack.push(num1 * num2);
                        break;
                    case "/":
                        stack.push(num1 / num2);
                        break;
                    default:
                }
            }
        }
        return stack.pop();
    }

    public boolean isNumber(String token) {
        return !("+".equals(token) || "-".equals(token) || "*".equals(token) || "/".equals(token));
    }
}

因为出现了对应关系,就联想想到了HashMap,但运算符作为value,代码用map.get()放到两个计算数字中间无法编译成功,就只能存储两个数字之间计算关系,又要引入函数式编程,下面代码简单看看了解一下就行

class Solution {
    public int evalRPN(String[] tokens) {
        Map<String, BiFunction<Integer, Integer, Integer>> operators = new HashMap<>();
        //key是运算符,value是”计算关系“
        operators.put("+", (a, b) -> a + b);
        operators.put("-", (a, b) -> a - b);
        operators.put("*", (a, b) -> a * b);
        operators.put("/", (a, b) -> a / b);

        Deque<Integer> stack = new LinkedList<>();
        for (String token : tokens) {
            if (operators.containsKey(token)) {
                int num2 = stack.pop();
                int num1 = stack.pop();
//函数式编程重要接口 BiFunction,根据token获取对应的计算关系operation
                BiFunction<Integer, Integer, Integer> operation = operators.get(token);
    			//计算结果后再将结果入栈
                stack.push(operation.apply(num1, num2));
            } else {
                stack.push(Integer.parseInt(token));
            }
        }
        
        return stack.pop();
    }
}
  • 37
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
邓俊辉教授是计算机科学与技术领域著名的教育家和研究者。他在清华大学担任教授,并负责计算机算法与理论方向的研究和教学工作。邓俊辉教授是中国计算机学会副理事长、国际著名科技出版社Springer中国系列丛书主编、IEICE China Communications主编、Journal of Internet Technology编委、《数据结构与算法教程》作者等。 在邓俊辉教授的指导下,他办了多次Dijkstra算法训练营,旨在培养学生对于算法学习的兴趣与能力。Dijkstra算法是一种用于图论中求解最短路径问题的经典算法,具有广泛的应用领域,如路由算法、网络规划和GPS导航系统等。在训练营中,邓俊辉教授通过讲解算法的原理和思想,引导学生进行编程实践和案例分析,帮助他们深入理解Dijkstra算法的应用场景与实际解决问题的能力。 邓俊辉教授所组织的Dijkstra算法训练营受到了广大学生的欢迎和积极参与。通过训练营的学习,学生不仅可以掌握Dijkstra算法的具体实现过程,还能了解算法设计的思路和应用的局限性。在训练营中,学生还可以与同学们进行交流和合作,共同解决实际问题,促进彼此的学术成长和人际交往能力的培养。 总之,邓俊辉的Dijkstra算法训练营为学生提供了一个系统、全面学习算法知识的平台,帮助他们培养解决实际问题的能力和思维方式。通过这样的培训,学生不仅能在学术领域取得更好的成绩,还可以为将来的职业发展打下坚实的基础。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值