小白学java栈的经典算法问题——第四关白银挑战

news2024/11/19 11:19:40
内容1.括号匹配问题
2.最小栈
3.最大栈

1.括号匹配问题

栈的典型题目还是非常明显的,括号匹配、表达式计算等等几乎都少不了栈,本小节我们就看两个最经典的问题

首先是LeetCode20,链接

 本道题还是比较简单的,其中比较麻烦的是如何判断两个符号是不是一组的

我们可以用哈希表将所有符号先存储,左半边做key,右半边做 value。遍历字符串的时候,遇到左半边符号就入栈,遇到右半边符号就与栈顶的符号比较,不匹配就返回false

class Solution {
    // 定义一个映射表,用于匹配括号
    private static final Map<Character, Character> map = new HashMap<Character, Character>() {{
        put('{', '}');
        put('[', ']');
        put('(', ')');
        put('?', '?');
    }};

    public boolean isValid(String s) {
        // 若字符串长度大于0且首字符不在映射表中,则直接返回false
        if (s.length() > 0 && !map.containsKey(s.charAt(0))) {
            return false;
        }

        // 使用链表作为栈的实现,初始化时添加一个占位符'?'
        LinkedList<Character> stack = new LinkedList<Character>() {{
            add('?');
        }};

        // 遍历字符串中的每个字符
        for (Character c : s.toCharArray()) {
            if (map.containsKey(c)) {  // 若字符为左括号,则将其入栈
                stack.addLast(c);
            } else if (map.get(stack.removeLast()) != c) {  // 若字符为右括号,则与栈顶字符进行匹配
                return false;
            }
        }

        // 最终,若栈中只剩下一个占位符'?',则所有括号都匹配成功
        return stack.size() == 1;
    }
}

这种问题使用java还是比较方便,但是对于C语言就非常不友好,需要自己构造,然后再实现算法。

LeetCode给我们制造了十几个括号匹配问题,但是都是条件变来变去,解决起来有难有易。

例如,LeetCode22链接

代码如下:

import java.util.ArrayList;
import java.util.List;

public class Solution {

    // 做减法

    public List<String> generateParenthesis(int n) {
        List<String> res = new ArrayList<>();
        // 特判
        if (n == 0) {
            return res;
        }

        // 执行深度优先遍历,搜索可能的结果
        dfs("", n, n, res);
        return res;
    }

    /**
     * 执行深度优先遍历,搜索可能的括号组合
     * @param curStr 当前递归得到的结果
     * @param left   剩余可用的左括号个数
     * @param right  剩余可用的右括号个数
     * @param res    结果集
     */
    private void dfs(String curStr, int left, int right, List<String> res) {
        // 因为每一次尝试,都使用新的字符串变量,所以无需回溯
        // 在递归终止的时候,直接把它添加到结果集即可,注意与「力扣」第 46 题、第 39 题区分
        if (left == 0 && right == 0) {
            res.add(curStr);
            return;
        }

        // 剪枝(如图,左括号可以使用的个数严格大于右括号可以使用的个数,才剪枝,注意这个细节)
        if (left > right) {
            return;
        }

        if (left > 0) {
            dfs(curStr + "(", left - 1, right, res); // 尝试放一个左括号
        }

        if (right > 0) {
            dfs(curStr + ")", left, right - 1, res); // 尝试放一个右括号
        }
    }
}

 LeetCode32最长有效括号

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; // 返回最长有效括号的长度
    }
}

 LeetCode301链接

 

class Solution {
    private List<String> res = new ArrayList<String>(); // 用于存储有效的括号组合

    public List<String> removeInvalidParentheses(String s) {
        int lremove = 0; // 需要移除的左括号数量
        int rremove = 0; // 需要移除的右括号数量

        for (int i = 0; i < s.length(); i++) {
            if (s.charAt(i) == '(') {
                lremove++; // 统计左括号数量
            } else if (s.charAt(i) == ')') {
                if (lremove == 0) {
                    rremove++; // 统计右括号数量,如果没有对应的左括号,则需要移除该右括号
                } else {
                    lremove--; // 匹配到一个右括号,可以抵消一个左括号
                }
            }
        }
        helper(s, 0, lremove, rremove); // 辅助函数进行递归处理

        return res; // 返回有效的括号组合列表
    }

    private void helper(String str, int start, int lremove, int rremove) {
        if (lremove == 0 && rremove == 0) { // 当需要移除的括号数量为0时,判断当前字符串是否有效
            if (isValid(str)) {
                res.add(str); // 如果有效,则将其添加到结果列表中
            }
            return; // 结束递归
        }

        for (int i = start; i < str.length(); i++) {
            if (i != start && str.charAt(i) == str.charAt(i - 1)) {
                continue; // 避免重复计算,如果当前字符和前一个字符相同,则跳过
            }
            // 如果剩余的字符无法满足去掉的数量要求,直接返回
            if (lremove + rremove > str.length() - i) {
                return;
            }
            // 尝试去掉一个左括号
            if (lremove > 0 && str.charAt(i) == '(') {
                helper(str.substring(0, i) + str.substring(i + 1), i, lremove - 1, rremove);
            }
            // 尝试去掉一个右括号
            if (rremove > 0 && str.charAt(i) == ')') {
                helper(str.substring(0, i) + str.substring(i + 1), i, lremove, rremove - 1);
            }
        }
    }

    private boolean isValid(String str) {
        int cnt = 0; // 记录左括号和右括号的差值
        for (int i = 0; i < str.length(); i++) {
            if (str.charAt(i) == '(') {
                cnt++; // 遇到左括号,增加差值
            } else if (str.charAt(i) == ')') {
                cnt--; // 遇到右括号,减少差值
                if (cnt < 0) {
                    return false; // 如果差值小于0,说明右括号多余左括号,无效
                }
            }
        }

        return cnt == 0; // 差值为0,说明左右括号一一对应,有效
    }
}

2.最小栈

LeetCoede155链接

本道题的关键在于理解getMin()到底表示什么,可以看一个例子上的示例画成示意图如下: 

 

这里的关键是理解对应的Min栈内,中间元素为什么是-2,理解了本道题就非常简单。

题目要求在常数事件内获得栈的最小值,因此不能在getMin()的时候再去计算最小值,最好在push或者pop的时候就已经计算好了当前栈中的最小值。

对于栈来说,如果一个元素a在入栈时,栈里有其他元素的b,c,d,那么无论这个栈在之后经历了什么操作,只要a在栈中,b,c,d就一定在栈中,因为在a被弹出之前,b,c,d就不会被弹出。

那么,我们可以在每个元素a入栈的时候把当前栈的最小值m存储起来,在这之后无论何时,如果栈顶元素是a,我们就可以直接返回存储的最小值m。

按照上面的思路,我们只需要设计一个数据结构,使得每一个元素a与其相应的最小值m时刻保持一一对应,因此我们使用一个辅助栈,与元素栈同步插入与删除,用于存储与每个元素对应的最小值。

  • 当一个元素要入栈的时候,我们取当前辅助栈的栈顶存储的最小值,与当前元素比较得出最小值,将这个最小值插入辅助栈中。
  • 当一个元素要出栈的时候,我们把辅助栈的栈顶元素也一并弹出。
  • 在任意一个时刻,栈内元素的最小值就存储在辅助栈的栈顶元素中。
/**
 * 实现一个最小栈 MinStack,支持 push、pop、top 和 getMin 操作
 */
class MinStack {
    private Stack<Integer> stack;  // 存储栈元素的主栈
    private Stack<Integer> min_stack;  // 存储当前栈中最小值的辅助栈

    public MinStack() {
        stack = new Stack<>();
        min_stack = new Stack<>();
    }

    /**
     * 向栈中压入元素x
     * @param x 待压入的元素
     */
    public void push(int x) {
        stack.push(x);  // 将元素压入主栈中
        if (min_stack.isEmpty() || x <= min_stack.peek()) {
            min_stack.push(x);  // 如果是最小值,则将其压入辅助栈中
        }
    }

    /**
     * 弹出栈顶元素
     */
    public void pop() {
        if (stack.pop().equals(min_stack.peek())) {
            min_stack.pop();  // 如果是最小值,则从辅助栈中弹出
        }
    }

    /**
     * 获取栈顶元素
     * @return 栈顶元素
     */
    public int top() {
        return stack.peek();  // 直接返回主栈的栈顶元素
    }

    /**
     * 获取栈中最小值
     * @return 栈中最小值
     */
    public int getMin() {
        return min_stack.peek();  // 直接返回辅助栈的栈顶元素,即为栈中最小值
    }
}

 3.最大栈

LeetCode716.设计一个最大栈数据结构,既支持栈操作,又支持查找栈中的最大元素。

实现MaxStack类:

MaxStack()初始化栈对象
void push(int x)将元素x压入栈中。
int pop()移除栈顶元素并返回这个元素
int top()返回栈顶元素,无需移除
int peeMax()检索并返回栈中最大元素,无需移除。
int popMax()检查并返回栈中最大元素,并将其移除。
如果有多个元素,只要移除 最靠近栈顶的那个。

示例:

输入:
["MaxStack","push","push","top","popMax","top","premix","pop","top"]
[[],[5],[1],[5],[],[],[],[],[],[]]
输出:
[null,null,null,null,5,5,1,5,1,5]
解释:
MaxStack stk=new MaxStack();
stk.push(5);   //[5] - 5既是栈顶元素,也是最大元素
stk.push(1);   //[5,1】 - 栈顶元素是1,最大元素是5
stk.push(5);   //[5,1,5] - 5既是栈顶元素,也是最大元素
stk.top();     //返回 5,[5,1,5] - 栈没有改变
stk.popMax();  //返回 5,[5,1] - 栈发生改变,栈顶元素不再是最大元素
stk.top();    //返回 1,[5,1] - 栈没有改变
stk.popMax(); //返回 5,[5,1] - 栈没有改变
stk.pop();     //返回 1,[5] - 次操作后,5既是栈顶元素,也是最大元素
stk.top();    //返回5,[5] - 栈没有改变

 本道题与上一题相反,但是处理方法是一致的,一个普通的栈可以支持前三种操作push(x)、pop()和top(),所以我们需要考虑的仅仅为后两种操作的peekMax()和popMax()。

对于peekMax(),我们可以另一个栈来存储每个位置到栈底的所有元素的最大值,例如,如果当前的第一个栈中元素为[2,1,5,3,9],那么第二个栈中的额元素为[2,2,5,5,9]。在push(x)操作时,只需要将第二个栈的栈顶和x的最大值入栈,而在pop()操作时,只需要将第二个栈进行出栈。

对于popMax(),由于我们知道当前栈中最大的元素值,因此可以直接将两个栈同时出栈,并存储第一个栈出栈的所有值。当某个时刻,第一个栈的出栈元素就等于当前栈中最大的元素值时,就找到了最大的元素。这个时候我们将之前第一个栈的所有元素重新入栈,并且同步更新第二个栈,就完成了popMax()操作。

package com.yugutou.charpter4_stack.level2;

import java.util.Stack;

/**
 * 实现一个支持 push、pop、top、peekMax 和 popMax 操作的最大栈 MaxStack
 */
class MaxStack {
    Stack<Integer> stack;  // 存储栈元素的主栈
    Stack<Integer> maxStack;  // 存储当前栈中最大值的辅助栈

    public MaxStack() {
        stack = new Stack();  // 初始化主栈
        maxStack = new Stack();  // 初始化最大值辅助栈
    }

    /**
     * 向栈中压入元素x
     * @param x 待压入的元素
     */
    public void push(int x) {
        int max = maxStack.isEmpty() ? x : maxStack.peek();  // 获取当前最大值
        maxStack.push(max > x ? max : x);  // 将当前最大值或新元素压入最大值辅助栈
        stack.push(x);  // 将元素压入主栈
    }

    /**
     * 弹出栈顶元素
     * @return 弹出的栈顶元素
     */
    public int pop() {
        maxStack.pop();  // 弹出最大值辅助栈的栈顶元素
        return stack.pop();  // 弹出主栈的栈顶元素
    }

    /**
     * 获取栈顶元素
     * @return 栈顶元素
     */
    public int top() {
        return stack.peek();  // 直接返回主栈的栈顶元素
    }

    /**
     * 获取栈中最大值
     * @return 栈中最大值
     */
    public int peekMax() {
        return maxStack.peek();  // 直接返回最大值辅助栈的栈顶元素,即为栈中最大值
    }

    /**
     * 弹出栈中的最大值
     * @return 被弹出的最大值
     */
    public int popMax() {
        int max = peekMax();  // 获取当前栈中的最大值
        Stack<Integer> buffer = new Stack();  // 辅助栈用于暂存元素
        while (top() != max) {
            buffer.push(pop());  // 将非最大值的元素暂存到辅助栈中
        }
        pop();  // 弹出栈中的最大值
        while (!buffer.isEmpty()) {
            push(buffer.pop());  // 将暂存的元素重新压入栈中
        }
        return max;  // 返回被弹出的最大值
    }
}

学习算法的时候真的是很费脑,但是看懂之后又觉得自己很厉害,自己走了很多步,再回头看,原来也不过如此,敢于尝试真的是勇敢的象征。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1294575.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Nacos未授权访问

漏洞描述 Nacos 是阿里巴巴推出来的一个新开源项目&#xff0c;是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。致力于帮助发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集&#xff0c;可以快速实现动态服务发现、服务配置、服务元数据及流量管…

轨道电流检测IC——FP355,助力蓄电池充电器、SPS(适配器)、电池管理系统、多口快充充电器的优雅升级

目录 一、FP355概述 二、FP355特点 三、FP355应用 随着移动设备的普及和人们对电力需求的不断增长&#xff0c;充电器的安全性和充电效率成为了重要的关注点。 作为一种能够精确检测电流的集成电路&#xff0c;轨道电流检测IC——FP355是个不错的选择。它不仅广泛应用于蓄电…

无公网IP环境如何SSH远程连接Deepin操作系统

文章目录 前言1. 开启SSH服务2. Deppin安装Cpolar3. 配置ssh公网地址4. 公网远程SSH连接5. 固定连接SSH公网地址6. SSH固定地址连接测试 前言 Deepin操作系统是一个基于Debian的Linux操作系统&#xff0c;专注于使用者对日常办公、学习、生活和娱乐的操作体验的极致&#xff0…

Bert-vits2新版本V2.1英文模型本地训练以及中英文混合推理(mix)

中英文混合输出是文本转语音(TTS)项目中很常见的需求场景&#xff0c;尤其在技术文章或者技术视频领域里&#xff0c;其中文文本中一定会夹杂着海量的英文单词&#xff0c;我们当然不希望AI口播只会念中文&#xff0c;Bert-vits2老版本(2.0以下版本)并不支持英文训练和推理&…

【CSP】202303-1_田地丈量Python实现

文章目录 [toc]试题编号试题名称时间限制内存限制问题描述输入格式输出格式样例输入样例输出样例解释子任务Python实现 试题编号 202303-1 试题名称 田地丈量 时间限制 1.0s 内存限制 512.0MB 问题描述 西西艾弗岛上散落着 n n n块田地&#xff0c;每块田地可视为平面直角坐标…

菜鸟学习日记(python)——推导式

python中的推导式是一种独特的数据处理方式&#xff0c;可以从一个数据序列去构建另一个新的数据序列的结构体。 它包括以下推导式&#xff1a; 列表&#xff08;list&#xff09;推导式字典&#xff08;dict&#xff09;推导式集合&#xff08;set&#xff09;推导式元组&am…

在AWS Lambda上部署标准FFmpeg工具——自定义层的方案

大纲 1 确定Lambda运行时环境1.1 Lambda系统、镜像、内核版本1.2 运行时1.2.1 Python1.2.2 Java 2 打包FFmpeg3 创建Lambda的Layer4 测试4.1 创建Lambda函数4.2 附加FFmpeg层4.3 添加测试代码4.4 运行测试 参考文献 FFmpeg被广泛应用于音/视频流处理领域。对于简单的需求&#…

刷题记录--算法--简单

第一题 2582. 递枕头 已解答 简单 相关标签 相关企业 提示 n 个人站成一排&#xff0c;按从 1 到 n 编号。 最初&#xff0c;排在队首的第一个人拿着一个枕头。每秒钟&#xff0c;拿着枕头的人会将枕头传递给队伍中的下一个人。一旦枕头到达队首或队尾&#xff0c;传递…

VUE2+THREE.JS 设定巡航行动轨迹

设定巡航行动轨迹 引入three.path初始化坐标点animate 执行行动轨迹动画参考博客 我们写3D时&#xff0c;常常会有按照一定轨迹去浏览模型&#xff0c; 所以,我们要先确认行动轨迹&#xff0c;渲染出行动轨迹以后&#xff0c;再让人物按照行动轨迹去移动 引入three.path cnpm …

ssm校园论坛管理系统项目分享

校园论坛管理系统是基于java编程语言&#xff0c;mysql数据库&#xff0c;ssm框架和idea工具开发&#xff0c;本系统主要分为学生用户&#xff0c;管理员两个角色&#xff0c;其中用户可以注册登陆系统&#xff0c;在线发帖&#xff0c;查看栏目帖子&#xff0c;回复帖子&#…

【数据分析实战】酒店行业华住集团门店分布与评分多维度分析

文章目录 1. 写在前面2. 数据集展示3. 多维度分析3.1 门店档次多元化&#xff1a;集团投资战略观察3.1.1 代码实现3.1.2 本人浅薄理解 3.2 门店分布&#xff1a;各省市分布概览3.2.1 代码实现3.2.2 本人浅薄理解 3.3 门店分级评分&#xff1a;服务水平的多维度观察3.3.1 代码实…

远程服务器QEMU+Ubuntu+GRUB+VNC最佳实践

远程服务器QEMUUbuntuGRUBVNC最佳实践 1. 准备2. QEMU启动安装Ubuntu2.1 服务器端2.2 本地端 3. 从服务器终端控制虚拟机GRUB与虚拟机终端 这段时间参与大量内核切换测试工作&#xff0c;实体机需要硬件自检太过笨重&#xff0c;因此主要通过QEMU验证正确性。有一个很大的问题是…

开源项目CuteSqlite开发笔记(二):SQLite的架构

在开发CuteSqlite图形客户端的时候&#xff0c;需要用到SQL的语法解释&#xff0c;来对SQL语句进行优化。找了很多的SQL语法解释器&#xff0c;都不是十分满意&#xff0c;只有翻开Sqlite的源码&#xff0c;看看SQLite对SQL语句的解释过程&#xff0c;本文是翻译的官方文档。 官…

模块电源(六):前馈电容

一、前馈电容&#xff1a; 前馈电容是与电阻分压的顶部电阻 并联的"可选电容器" 二、计算及仿真&#xff1a; 1、计算 无前馈电容时&#xff0c;输出电压&#xff1a;&#xff1b;有前馈电容时&#xff0c;输出电压&#xff1a;&#xff0c;(其中&#xff0c;&am…

如何解决5G基站高能耗问题?

安科瑞 须静燕 截至2023年10月&#xff0c;我国5G基站总数达321.5万个&#xff0c;占全国通信基站总数的28.1%。然而&#xff0c;随着5G基站数量的快速增长&#xff0c;基站的能耗问题也逐渐日益凸显&#xff0c;基站的用电给运营商带来了巨大的电费开支压力&#xff0c;降低5…

JavaSE基础50题:17. (递归)求1+2+3+……+10

概述 递归求123……10 代码 public class P17 {public static int sum(int n) {if (n 1) {return 1;}return n sum(n-1);}public static void main(String[] args) {System.out.println(sum(10)); //55} }运行过程 如&#xff1a;1234&#xff0c;最后的结果为10

Nacos源码解读07——集群数据同步

Distro协议背景 Distro 协议是 Nacos 社区自研的⼀种 AP 分布式协议&#xff0c;是面向临时实例设计的⼀种分布式协议&#xff0c; 其保证了在某些 Nacos 节点宕机后&#xff0c;整个临时实例处理系统依旧可以正常工作。作为⼀种有状态 的中间件应用的内嵌协议&#xff0c;Dis…

柏林噪声C++

柏林噪声 随机噪声 如上图所示随机噪声没有任何规律可言&#xff0c;我们希望生成有一些意义的局部连续的随机图案 一维柏林噪声 假设希望生成一段局部连续的随机曲线&#xff0c;可以采用插值的方式&#xff1a;在固定点随机分配y值&#xff08;一般是整数点&#xff09;&a…

docker安装elasticsearch8.5.0和kibana

服务器环境&#xff0c;centos7 一、安装elasticsearch 1. 创建一个es和kibana通用的网络 docker network create es-net 2. 拉取es镜像&#xff0c;这里选择8.5.0版本 docker pull elasticsearch:8.5.03. 创建挂载目录&#xff0c;并授权 mkdir /usr/local/install/ela…

WebStorm:Mac/Win上强大的JavaScript开发工具

WebStorm是JetBrains公司开发的针对Mac和Windows系统的JavaScript开发工具。它为开发者提供了一站式的代码编辑、调试、测试和版本控制等功能&#xff0c;帮助你更高效地进行Web开发。新版本的WebStorm 2023在性能和用户体验方面都做出了重大改进&#xff0c;让你的JavaScript开…