算法宝典1——Java版本(此系列持续更新,这篇文章有20道)(有题目的跳转链接)(此份宝典包含了链表、栈、队列、二叉树的算法题)(下)

简介: 算法宝典1——Java版本(此系列持续更新,这篇文章有20道)(有题目的跳转链接)(此份宝典包含了链表、栈、队列、二叉树的算法题)(下)

【扩展问题】

  • 为什么快指针每次走两步,慢指针走一步可以?

假设链表带环,两个指针最后都会进入环,快指针先进环,慢指针后进环。当慢指针刚进环时,可能就和快指针相遇了,最差情况下两个指针之间的距离刚好就是环的长度。此时,两个指针每移动一次,之间的距离就缩小一步,不会出现每次刚好是套圈的情况,因此:在慢指针走到一圈之前,快指针肯定是可以追上慢指针的,即相遇。

  • 快指针一次走3步,走4步,...n步行吗?

10.  环形链表 II(力扣;思路:快慢指针、数学追击问题★★★☆☆)

题目:给定一个链表的头节点  head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos-1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

不允许修改 链表。

思路:

代码:

public ListNode detectCycle() {
        //找到相遇点
        ListNode fast = head;
        ListNode slow = head;
        while(fast != null && fast.next != null){
            fast = fast.next.next;
            slow = slow.next;
            if(fast == slow){
                break;
            }
        }
        if(fast  == null || fast.next == null){
            return null;
        }
        //slow指针从头走
        slow = head;
        //fast指针从相遇点开始走
        //因为X = (N - 1)C + Y
        while (fast != slow){
            fast = fast.next;
            slow = slow.next;
        }
        return fast;
    }

运行结果:

二、栈的算法题(目前4道)

1. 有效的括号(力扣)

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

有效字符串需满足:

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

思路:

代码:

public static boolean isValid(String s) {
        //创建一个栈来存储括号
        Stack<Character> stack = new Stack<>();
        //用for循环进行拆分String
        for (int i = 0; i < s.length(); i++) {
            char ch = s.charAt(i);
            //如果字符是左括号就入栈
            if (ch == '(' || ch == '{' || ch == '[') {
                stack.push(ch);
            } else {
                //如果字符是右括号就执行else
                //如果栈是空的,就意味着右括号之前没有左括号
                if (stack.empty()) {
                    return false;
                }
                //先看下栈顶元素是不是满足条件的左括号
                char ch2 = stack.peek();
                //进行匹配
                if (ch == ')' && ch2 == '(' || ch == '}' && ch2 == '{'  || ch == ']' && ch2 == '['){
                    //满足条件,栈顶的左括号跳出
                    stack.pop();
                }else{
                    return false;//如果不满足条件,返回false
                }
            }
        }
        //假设右括号都匹配完了,还是有左括号加入,也就是栈不为空
        if(!stack.empty()){
            return false;
        }
        return true;
    }

运行结果:

2. 逆波兰表达式求值(力扣)

题目:给你一个字符串数组 tokens ,表示一个根据 逆波兰表示法 表示的算术表达式。

请你计算该表达式。返回一个表示表达式值的整数。

注意:

  • 有效的算符为 '+''-''*''/'
  • 每个操作数(运算对象)都可以是一个整数或者另一个表达式。
  • 两个整数之间的除法总是 向零截断
  • 表达式中不含除零运算。
  • 输入是一个根据逆波兰表示法表示的算术表达式。
  • 答案及所有中间计算结果可以用 32 位 整数表示。

思路:

代码:

public int evalRPN(String[] tokens) {
        //首先创建一个栈
        Stack<Integer> stack = new Stack<>();
        //遍历数组
        for(String x : tokens){
            if (!isOperation(x)){
                //如果不是操作符就压入栈
                stack.push(Integer.parseInt(x));//解析字符串
            }else{
                //如果遍历到操作符
                //弹出两个数
                int num2 = stack.pop();//作为右操作数
                int num1 = stack.pop();//作为左操作数
                switch (x){
                    case "+":
                        stack.push(num1 + num2);
                        break;
                    case "-":
                        stack.push(num1 - num2);
                        break;
                    case "*":
                        stack.push(num1 * num2);
                        break;
                    case "/":
                        stack.push(num1 / num2);
                        break;
                }
            }
        }
        //最后弹出最后一个数,就可以得到结果了
        return stack.pop();
    }
    //判断传入的字符是不是操作数
    private boolean isOperation(String x){
        if(x.equals("+") || x.equals("-") || x.equals("*") || x.equals("/")){
            return true;
        }
        return false;
    }

运行结果:

3. 栈的压入、弹出序列(牛客网)

题目:输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。

1. 0<=pushV.length == popV.length <=1000

2. -1000<=pushV[i]<=1000

3. pushV 的所有数字均不相同

思路:此题的思路比较简单,就不作图了,代码的注释有写哈哈哈。

代码:

public boolean IsPopOrder (int[] pushV, int[] popV) {
        //pushV 和 popV 的元素数量都一致
        // write code here
        //首先创建一个栈
        Stack<Integer> stack = new Stack<>();
        int j = 0;
        for (int i = 0;i < pushV.length;i++){
            stack.push(pushV[i]);
            //这段代码要注意异常的抛出,得先知道栈不为空,才可以跳出栈顶元素
            while(j < popV.length && !stack.empty() && stack.peek().equals(popV[j])  ){
                stack.pop();
                j++;
            }
        }
        return stack.empty();//如果stack为空了,就是栈内没元素了,都匹配上了
    }

运行结果:

4.  最小栈(力扣)

题目:设计一个支持 pushpoptop 操作,并能在常数时间内检索到最小元素的栈。

实现 MinStack 类:

  • MinStack() 初始化堆栈对象。
  • void push(int val) 将元素val推入堆栈。
  • void pop() 删除堆栈顶部的元素。
  • int top() 获取堆栈顶部的元素。
  • int getMin() 获取堆栈中的最小元素。

思路:

虽然内部是用了两个栈,但是在外部看就是一个栈!

代码:

public class MinStack {
    private Stack<Integer> stack;
    private Stack<Integer> minStack;
    //初始化两个栈
    public MinStack() {
        stack = new Stack<>();
        minStack = new Stack<>();
    }
    //压栈
    public void push(int val) {
        //先压入普通栈
        stack.push(val);
        //如果最小栈为空,直接压入最小栈进行维护
        if(minStack.empty()){
            minStack.push(val);
        }else{
            //如果最小栈有值,那么就看看如今想压入栈的元素与栈顶元素的大小
            //栈顶元素是在minStack中最小的元素,也是stack中最小的元素
            if (val <= minStack.peek()){
                minStack.push(val);
            }
        }
    }
    //出栈
    public void pop() {
        if(!stack.empty()){
            Integer val = stack.pop();//取出stack的栈顶元素
            //如果stack的栈顶元素是minStack栈顶元素的,那么minStack的栈顶元素也要取出
            if(val.equals(minStack.peek())){//这里必须用equals,因为Integer在元数据区中只有-128~127
                minStack.pop();
            }
        }
    }
    //查看栈顶元素
    public int top() {
        return stack.peek();
    }
    public int getMin() {
        return minStack.peek();
    }
}

运行结果:

三、队列的算法题(目前3道)

1. 设计循环队列(力扣)

题目:设计你的循环队列实现。 循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。

循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。

你的实现应该支持如下操作:

  • MyCircularQueue(k): 构造器,设置队列长度为 k 。
  • Front: 从队首获取元素。如果队列为空,返回 -1 。
  • Rear: 获取队尾元素。如果队列为空,返回 -1 。
  • enQueue(value): 向循环队列插入一个元素。如果成功插入则返回真。
  • deQueue(): 从循环队列中删除一个元素。如果成功删除则返回真。
  • isEmpty(): 检查循环队列是否为空。
  • isFull(): 检查循环队列是否已满。

思路:

循环队列两个特殊的状态:队空和队满(rear的情况和front类似)。 由图3-5可以看出,循环队列必须损失一个存储空间,如果空白处也存入元素,则队满的条件也成了front==rear,即和队空条件相同,那么就无法区分队空和队满了

(1)三个状态

1)队空状态:qu.rear==qu.front。

2)队满状态:(qu.rear+1)%maxSizequ == front。

3)长度(多少个元素):(rear-front+maxsize)%maxsize;防止rear-front为负,所以要加maxsize

(2)两个操作

1)元素x进队操作(移动队尾指针)。

qu.rear=(qu.rear+1)%maxSize;qu.data[qu.rear]=x;

2)元素x出队操作(移动队首指针)。

qu.front=(qu.front+1)%maxSize;x=qu.data[qu.front];

队首指针指向的位置不能有数据!!!

代码:

//循环队列
public class MyCircularQueue {
    private int elem[];
    private int front;//表示队列的头
    private int rear;//表示队列的尾
    //创建循环队列
    public MyCircularQueue(int k) {
        //如果是浪费空间,这里必须多加一个1
        this.elem = new int[k + 1];
    }
    //判断队列是否为满
    public boolean isFull() {
        return (rear + 1) % elem.length == front;
    }
    //入队列
    public boolean enQueue(int value) {
        //1. 检查是否队列是满的
        if (isFull()) {
            return false;
        }
        elem[rear] = value;
        rear = (rear + 1) % elem.length;
        return true;
    }
    //出队列
    public boolean deQueue() {
        //队列为空
        if (isEmpty()) {
            return false;
        }
        front = (front + 1) % elem.length;
        return true;
    }
    //得到队头元素
    public int Front() {
        if (isEmpty()) {
            return -1;
        }
        return elem[front];
    }
    //得到队尾元素
    public int Rear() {
        if(isEmpty()) {
            return -1;
        }
        int index = (rear == 0) ? elem.length - 1 : rear-1;
        return elem[index];
    }
    //判读队列是否为空
    public boolean isEmpty() {
        return front == rear;
    }
}

运行结果:

2. 用队列实现栈(力扣)

题目:请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(pushtoppopempty)。

实现 MyStack 类:

  • void push(int x) 将元素 x 压入栈顶。
  • int pop() 移除并返回栈顶元素。
  • int top() 返回栈顶元素。
  • boolean empty() 如果栈是空的,返回 true ;否则,返回 false

思路:

代码:

import java.util.LinkedList;
import java.util.Queue;
import java.util.Stack;
public class MyStack2 {
    //需要两个队列才能模拟栈
    private Queue<Integer> qu1;
    private Queue<Integer> qu2;
    public MyStack2() {
        qu1 = new LinkedList<>();
        qu2 = new LinkedList<>();
    }
    public void push(int x) {
        if(!qu1.isEmpty()){
            qu1.offer(x);
        }else if(!qu2.isEmpty()){
            qu2.offer(x);
        }else {
            qu1.offer(x);
        }
    }
    public int pop() {
        if(empty()){
            return -1;//两个队列都为空,意味着当前的栈为空
        }
        if(!qu1.isEmpty()){
            int size = qu1.size();
            for(int i = 0;i < size - 1;i++){
                int val = qu1.poll();
                qu2.offer(val);
            }
            return qu1.poll();
        }else{
            int size = qu2.size();
            for(int i = 0;i<size - 1;i++){
                int val = qu2.poll();
                qu1.offer(val);
            }
            return qu2.poll();
        }
    }
    public int top() {
        if(empty()){
            return -1;
        }
        int val = 0;
        if(!qu1.isEmpty()){
            int size = qu1.size();
            for(int i = 0;i < size;i++){
                val = qu1.poll();
                qu2.offer(val);
            }
            return val;
        }else{
            int size = qu2.size();
            for(int i = 0;i < size;i++){
                val = qu2.poll();
                qu1.offer(val);
            }
            return val;
        }
    }
    public boolean empty() {
        return qu1.isEmpty() && qu2.isEmpty();
    }
}

运行结果:

3. 用栈实现队列(力扣)

题目:请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(pushpoppeekempty):

实现 MyQueue 类:

  • void push(int x) 将元素 x 推到队列的末尾
  • int pop() 从队列的开头移除并返回元素
  • int peek() 返回队列开头的元素
  • boolean empty() 如果队列为空,返回 true ;否则,返回 false

说明:

  • 只能 使用标准的栈操作 —— 也就是只有 push to top, peek/pop from top, size, 和 is empty 操作是合法的。
  • 你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。

思路:

代码:

import java.util.Stack;
public class MyQueue2 {
    //创建两个栈
    private Stack<Integer> s1;
    private Stack<Integer> s2;
    public MyQueue2() {
        s1 = new Stack<>();
        s2 = new Stack<>();
    }
    //入队
    public void push(int x) {
        s1.push(x);
    }
    //出队
    public int pop() {
        //栈空判断
        if(empty()){
            return -1;
        }
        if(s2.empty()){
            while(!s1.empty()){
                s2.push(s1.pop());
            }
        }
        return s2.pop();
    }
    //返回队头结点
    public int peek() {
        //栈空判断
        if(empty()){
            return -1;
        }
        if(s2.empty()){
            while(!s1.empty()){
                s2.push(s1.pop());
            }
        }
        return s2.peek();
    }
    public boolean empty() {
        return s1.empty() && s2.empty();
    }
}

运行结果:

四、二叉树的算法题(目前3道)

1. 相同的树(力扣)

题目:给你两棵二叉树的根节点 pq ,编写一个函数来检验这两棵树是否相同。

如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。

示例1:

输入:p = [1,2,3],q = [1,2,3]

输出:true

示例2:

输入:p = [1,2],q = [1,null,2]

输出:false

示例3:

输入:p = [1,2,1],q = [1,1,2]

输出:false

提示:

  • 两棵树上的节点数目都在范围 [0, 100]
  • -104 <= Node.val <= 104

思路:

代码:

//判断两棵树是否相同
    public boolean isSameTree(TreeNode p, TreeNode q) {
        //如果p还有结点,q没有结点,证明两棵树的子树就不相同了,反之亦然
        if(p != null && q == null || p == null && q != null){
            return false;
        }
        //要么都为null,要么都不为null
        if(p == null && q == null){
            return true;
        }
        //如果两个结点的数值不一样就返回false
        if(p.val != q.val){
            return false;
        }
        return isSameTree(q.left,p.left) && isSameTree(q.right,p.right);
    }

运行结果:

2. 另一棵树的子树(力扣)

题目:给你两棵二叉树 rootsubRoot 。检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树。如果存在,返回 true ;否则,返回 false

二叉树 tree 的一棵子树包括 tree 的某个节点和这个节点的所有后代节点。tree 也可以看做它自身的一棵子树。

示例1:

输入:root = [3,4,5,1,2],subRoot = [4,1,2]

输出:true

示例2:

输入:root = [3,4,5,1,2,null,null,null,null,0],subRoot = [4,1,2]

输出:false

提示:

  • root 树上的节点数量范围是 [1, 2000]
  • subRoot 树上的节点数量范围是 [1, 1000]
  • -104 <= root.val <= 104
  • -104 <= subRoot.val <= 104

思路:

代码:

//判断两棵树是否相同
    public boolean isSameTree(TreeNode p, TreeNode q) {
        //如果p还有结点,q没有结点,证明两棵树的子树就不相同了,反之亦然
        if(p == null && q != null || p != null && q == null){
            return false;
        }
        //要么都为null,要么都不为null
        if(p == null && q == null){
            return true;
        }
        //如果两个结点的数值不一样就返回false
        if(p.val != q.val){
            return false;
        }
        return isSameTree(p.left,q.left) && isSameTree(p.right,q.right);
    }
    public boolean isSubtree(TreeNode root, TreeNode subRoot) {
        if(root == null || subRoot == null){
            return false;
        }
        if(isSameTree(root,subRoot))//从当前结点开始
            return true;
        if(isSubtree(root.left,subRoot))//从左子树的根结点开始
            return true;
        if(isSubtree(root.right,subRoot))//从右子树的根结点开始
            return true;
        return false;
    }

运行结果:

3. 翻转二叉树(力扣)

题目:给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。

思路:

(1)先将父亲结点交换

(2)再将子树结点交换

(3)然后递归下去

代码:

//翻转二叉树
    public TreeNode invertTree(TreeNode root) {
        //如果为空树,返回null
        if(root == null){
            return null;
        }
        //进行交换
        TreeNode tmp = root.left;
        root.left = root.right;
        root.right = tmp;
        invertTree(root.left);//翻转左子树
        invertTree(root.right);//翻转右子树
        return root;
    }

运行结果:

相关文章
|
3月前
|
存储 算法 Java
惊!Java程序员必看:JVM调优揭秘,堆溢出、栈溢出如何巧妙化解?
【8月更文挑战第29天】在Java领域,JVM是代码运行的基础,但需适当调优以发挥最佳性能。本文探讨了JVM中常见的堆溢出和栈溢出问题及其解决方法。堆溢出发生在堆空间不足时,可通过增加堆空间、优化代码及释放对象解决;栈溢出则因递归调用过深或线程过多引起,调整栈大小、优化算法和使用线程池可有效应对。通过合理配置和调优JVM,可确保Java应用稳定高效运行。
135 4
|
1月前
|
存储 算法 搜索推荐
探索常见数据结构:数组、链表、栈、队列、树和图
探索常见数据结构:数组、链表、栈、队列、树和图
85 64
|
6天前
|
算法 安全 NoSQL
2024重生之回溯数据结构与算法系列学习之栈和队列精题汇总(10)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第3章之IKUN和I原达人之数据结构与算法系列学习栈与队列精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
9天前
|
存储 算法 Java
🧠Java零基础 - Java栈(Stack)详解
【10月更文挑战第17天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
11 2
|
25天前
|
存储 算法 定位技术
数据结构与算法学习二、稀疏数组与队列,数组模拟队列,模拟环形队列
这篇文章主要介绍了稀疏数组和队列的概念、应用实例以及如何使用数组模拟队列和环形队列的实现方法。
19 0
数据结构与算法学习二、稀疏数组与队列,数组模拟队列,模拟环形队列
|
26天前
|
存储 安全 Java
【用Java学习数据结构系列】探索栈和队列的无尽秘密
【用Java学习数据结构系列】探索栈和队列的无尽秘密
28 2
|
2月前
|
存储 算法 前端开发
深入理解操作系统:进程调度与优先级队列算法
【9月更文挑战第25天】在操作系统的复杂世界中,进程调度是维持系统稳定运行的核心机制之一。本文将深入探讨进程调度的基本概念,分析不同的进程调度算法,并着重介绍优先级队列算法的原理和实现。通过简洁明了的语言,我们将一起探索如何优化进程调度,提高操作系统的效率和响应速度。无论你是计算机科学的初学者还是希望深化理解的专业人士,这篇文章都将为你提供有价值的见解。
|
2月前
|
Java API 容器
JAVA并发编程系列(10)Condition条件队列-并发协作者
本文通过一线大厂面试真题,模拟消费者-生产者的场景,通过简洁的代码演示,帮助读者快速理解并复用。文章还详细解释了Condition与Object.wait()、notify()的区别,并探讨了Condition的核心原理及其实现机制。
|
26天前
|
存储 算法 Java
【用Java学习数据结构系列】用堆实现优先级队列
【用Java学习数据结构系列】用堆实现优先级队列
29 0
|
Java
java实现简单的二叉树ADT
java实现简单的二叉树ADT
158 0