单向链表的结构
    image.png
    image.png

    image.png

    单链表反转
    设置pre前节点(反转最终指向)
    记录下一个节点的值 (当前.next)
    原下一个节点指向前一个节点
    前一个节点pre向后滑动指向当前节点(head);为之后 下一个节点 指向 前一个节点准备
    下一个节点成为头节点(head 滑倒下一个节点)
    image.png
    双向链表反转
    next指针指前一个 last指针指后一个
    image.png
    一个单链表给定头节点 一个额外的输入参数 num= 3 将链表中所有值为 3的删掉

    1. public static Node removeValue(Node head, int num) {
    2. // head来到第一个不需要删的位置
    3. while (head != null) {
    4. if (head.value != num) {
    5. break;
    6. }
    7. head = head.next;
    8. }
    9. // 1 ) head == null
    10. // 2 ) head != null
    11. Node pre = head;
    12. Node cur = head;
    13. while (cur != null) {
    14. if (cur.value == num) {
    15. //这个过程是指针重连节点(跳过要删的节点)
    16. //cur可以理解为引导节点(探索是否要删)
    17. //连到了cur命中了pre最近的不等于num的位置
    18. pre.next = cur.next;
    19. } else {
    20. //pre相当于上一个不等于num的位置
    21. pre = cur;
    22. }
    23. //相当于head.next 之后 cur值才变
    24. //并且cur相当于是head的下一个(与头节点建立了联系)
    25. cur = cur.next;
    26. }
    27. return head;
    28. }

    **Java 会产生内存泄露
    因为变量的生命周期
    JVM释放空间 现在正在生存的变量 指向 某一个正在使用的内存 JVM是不会释放的
    找不到 活着引用的空间 JVM会释放掉

    单向链表中 虽然只记录了 head节点 但后面的节点都不会被释放
    因为 后面的 节点 都会被一个生存着的 head节点引用
    没有被引用的节点 JVM会自动释放掉


    image.png

    image.png
    双向链表的节点类型
    image.png

    链表实现队列
    初始化链表结构

    1. public static class DoubleEndsQueue<T> {
    2. public Node<T> head;
    3. public Node<T> tail;

    头部加入节点

    1. public void addFromHead(T value) {
    2. Node<T> cur = new Node<T>(value);
    3. if (head == null) {
    4. head = cur;
    5. tail = cur;
    6. } else {
    7. //当前节点的下一个节点指向原来的头
    8. cur.next = head;
    9. //头的前指针指向当前节点
    10. head.last = cur;
    11. //当前节点做头
    12. head = cur;
    13. }
    14. }

    尾部加入节点(原理同头部)

    1. public void addFromBottom(T value) {
    2. Node<T> cur = new Node<T>(value);
    3. if (head == null) {
    4. head = cur;
    5. tail = cur;
    6. } else {
    7. cur.last = tail;
    8. tail.next = cur;
    9. tail = cur;
    10. }
    11. }

    头部弹出

    1. public T popFromHead() {
    2. if (head == null) {
    3. return null;
    4. }
    5. Node<T> cur = head;
    6. if (head == tail) {
    7. head = null;
    8. tail = null;
    9. } else {
    10. head = head.next;
    11. cur.next = null;
    12. head.last = null;
    13. }
    14. return cur.value;
    15. }

    尾部弹出

    1. public T popFromBottom() {
    2. if (head == null) {
    3. return null;
    4. }
    5. Node<T> cur = tail;
    6. if (head == tail) {
    7. head = null;
    8. tail = null;
    9. } else {
    10. tail = tail.last;
    11. tail.next = null;
    12. cur.last = null;
    13. }
    14. return cur.value;
    15. }

    验证是否为空 只要判断头是否为空

    public boolean isEmpty() {
                return head == null;
            }
    

    栈结构 利用队列 只头部加入 头部弹出

    public static class MyStack<T> {
            private DoubleEndsQueue<T> queue;
    
            public MyStack() {
                queue = new DoubleEndsQueue<T>();
            }
    
            public void push(T value) {
                queue.addFromHead(value);
            }
    
            public T pop() {
                return queue.popFromHead();
            }
    
            public boolean isEmpty() {
                return queue.isEmpty();
            }
    
        }
    

    队列的实现 (头部进 尾部出)

    public static class MyQueue<T> {
            private DoubleEndsQueue<T> queue;
    
            public MyQueue() {
                queue = new DoubleEndsQueue<T>();
            }
    
            public void push(T value) {
                queue.addFromHead(value);
            }
    
            public T poll() {
                return queue.popFromBottom();
            }
    
            public boolean isEmpty() {
                return queue.isEmpty();
            }
    
        }
    

    队列和栈都属对双链表的调整
    时间复杂度都是 O(1)
    因为都是对 head 和 tail 的操作 部牵扯到遍历

    数组实现栈和队列
    一般都是实现固定大小的栈或队列
    给定一个总大小(限定结构最多能存在多少个数)
    可以接受一次接收最大容量值 但超出就报错 (动态的变化要一直正确)

    数组实现队列
    维护两个指针
    一个弹出指针(索引) 一个放入指针(索引)
    一个已装入数量变量 size 一个固定不变的限制 limit
    整个过程
    相当于两个 索引指针循环 整个数组没有固定的头和尾 头和尾都由指针来维护(环形数组)

    public class Code04_RingArray {
    
        public static class MyQueue {
            private int[] arr;
            private int pushi;// end
            private int polli;// begin
            private int size;
            private final int limit;
    
            public MyQueue(int limit) {
                arr = new int[limit];
                pushi = 0;
                polli = 0;
                size = 0;
                this.limit = limit;
            }
    
            public void push(int value) {
                if (size == limit) {
                    throw new RuntimeException("队列满了,不能再加了");
                }
                size++;
                arr[pushi] = value;
                pushi = nextIndex(pushi);
            }
    
            public int pop() {
                if (size == 0) {
                    throw new RuntimeException("队列空了,不能再拿了");
                }
                size--;
                int ans = arr[polli];
                polli = nextIndex(polli);
                return ans;
            }
    
            public boolean isEmpty() {
                return size == 0;
            }
    
            // 如果现在的下标是i,返回下一个位置
            private int nextIndex(int i) {
                return i < limit - 1 ? i + 1 : 0;
            }
        }
    }
    

    image.png
    过程图解
    image.png
    准备两个栈 一个正常的data栈 一个最小min栈
    第一个数 data栈 和 min栈都加
    第二个数 data栈正常加
    min栈:当前的数和min栈的栈顶(最后压栈的数)相比 谁小加谁(存在重复加)
    弹出的时候 同步弹出 min栈弹出 不反回
    获取最小值 任何时候弹出min栈 栈顶就是最小值

    public static class MyStack2 {
            private Stack<Integer> stackData;
            private Stack<Integer> stackMin;
    
            public MyStack2() {
                this.stackData = new Stack<Integer>();
                this.stackMin = new Stack<Integer>();
            }
    
            public void push(int newNum) {
                if (this.stackMin.isEmpty()) {
                    this.stackMin.push(newNum);
                } else if (newNum < this.getmin()) {
                    this.stackMin.push(newNum);
                } else {
                    int newMin = this.stackMin.peek();
                    this.stackMin.push(newMin);
                }
                this.stackData.push(newNum);
            }
    
            public int pop() {
                if (this.stackData.isEmpty()) {
                    throw new RuntimeException("Your stack is empty.");
                }
                this.stackMin.pop();
                return this.stackData.pop();
            }
    
            public int getmin() {
                if (this.stackMin.isEmpty()) {
                    throw new RuntimeException("Your stack is empty.");
                }
                return this.stackMin.peek();
            }
        }
    

    第二种思路 省一点空间 费一点时间
    思路图解
    image.png
    data栈正常压入 min栈第一个正常压入
    之后的数据 data栈正常压入 min栈压入的时候 发现当前数比最小栈的栈顶大 不压入
    只有当前数 小于等于 min栈栈顶的时候才压入
    弹出的时候 data栈正常弹出 min栈弹出需要和data栈弹出数据比较 只有相等的时候才弹出

    image.png

    用队列结构实现栈
    图解
    image.png
    两个队列 一个data队列 一个help辅助队列
    data队列数据正常放入
    弹出的时候 将1234 导入help队列中 data对列只保留最后一个数据 返回
    下次弹出 两个队列功能交换 help保留最后一个数据返回 之前的数据导入data库

    public static class TwoQueueStack<T> {
            public Queue<T> queue;
            public Queue<T> help;
    
            public TwoQueueStack() {
                queue = new LinkedList<>();
                help = new LinkedList<>();
            }
    
            public void push(T value) {
                queue.offer(value);
            }
    
            public T poll() {
                while (queue.size() > 1) {
                    help.offer(queue.poll());
                }
                T ans = queue.poll();
                Queue<T> tmp = queue;
                queue = help;
                help = tmp;
                return ans;
            }
    
            public T peek() {
                while (queue.size() > 1) {
                    help.offer(queue.poll());
                }
                T ans = queue.poll();
                help.offer(ans);
                Queue<T> tmp = queue;
                queue = help;
                help = tmp;
                return ans;
            }
    
            public boolean isEmpty() {
                return queue.isEmpty();
            }
    
        }
    

    用栈结构实现队列
    过程图解
    image.png
    两个栈 一个push栈 一个pop栈
    push栈正常放数据 取数据的时候 正常弹出 放入pop栈
    放入pop栈前提条件
    1)导入前 pop栈为空 2)push栈要一次性全部导入pop栈

    public static class TwoStacksQueue {
            public Stack<Integer> stackPush;
            public Stack<Integer> stackPop;
    
            public TwoStacksQueue() {
                stackPush = new Stack<Integer>();
                stackPop = new Stack<Integer>();
            }
    
            // push栈向pop栈倒入数据
            private void pushToPop() {
                if (stackPop.empty()) {
                    while (!stackPush.empty()) {
                        stackPop.push(stackPush.pop());
                    }
                }
            }
    
            public void add(int pushInt) {
                stackPush.push(pushInt);
                pushToPop();
            }
    
            public int poll() {
                if (stackPop.empty() && stackPush.empty()) {
                    throw new RuntimeException("Queue is empty!");
                }
                pushToPop();
                return stackPop.pop();
            }
    
            public int peek() {
                if (stackPop.empty() && stackPush.empty()) {
                    throw new RuntimeException("Queue is empty!");
                }
                pushToPop();
                return stackPop.peek();
            }
        }