LeetCode算法(链表)

今天的算法是链表篇,这篇比较简单,总体是之前完成的手写链表,几乎就是链表的大部分知识了,所以今天算是一个复习内容了。

链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。

链表的入口节点称为链表的头结点也就是head。

所以第一步要设计一个节点类,如下:

java 复制代码
public class Node<E> {
    //节点内的数据
    E data;
    //节点指向的下一个对象
    Node<E> next;

    public Node(E data, Node<E> next) {
        this.data = data;
        this.next = next;
    }
}

下面我们开始手写单向链表。

1.尾部添加

链接:设计链表

首先构建一个自己的链表,属性为 链表的长度,链表的头节点

对于尾部添加需要注意的是,需要知道链表的尾部节点是什么,所以第一步应该写一个方法,遍历链表找到尾节点。我们只需要判断每个节点的下一个指针是否为空,空的就是尾部节点。如下

java 复制代码
    private Node<E> findEnd() {
        Node<E> node = first;
        while (node.next != null){
            node = node.next;
        }
        return node;
    }

然后就是尾部添加方法的实现。

思路:

(1)判断链表是否为空,如果为空,则该节点为头节点

(2)调用查询尾部节点的方法,拿到尾部节点

(3)尾部添加,长度加一

代码如下:

java 复制代码
    public void add(E data) {
        Node<E> node = new Node<>(data, null);
        //首先判断头节点是否为空,如果为空则证明为空链表,第一次添加的节点为头节点
        if (first == null) {
            first = node;
            size++;
            return;
        }
        //找到链表的尾部
        Node<E> endNode = findEnd();
        endNode.next = node;
        size++;
    }

2.删除节点

链接:设计链表****

删除节点有两种方式,一种是根据索引进行删除,另一种是根据节点内的数据进行删除

索引删除:

无论是删除还是添加,我们都需要注意,要考虑到头节点的情况,并对其做出相应的处理

首先需要一个方法,根据索引返回对应的节点,该方法不需要考虑头节点,因为我们需要在真正执行删除的方法中添加对应的逻辑就好,该方法为通用方法

举例:需要找到索引值为2的节点,那么在不考虑头节点的情况下,只需要找到1索引值的节点,就可以根据其next的指针,找到索引值为2的节点

node(2) = node(1).next 所以只需要传入1,就可以了

代码如下:

java 复制代码
    //寻找对于索引处的节点
    public Node<E> findNode(int index) {
        Node<E> node = first;
        for (int i = 0; i < index; i++) {
            node = node.next;
        }
        return node;
    }

下面开始删除节点的方法。

思路:

(1)如果索引为0,也就是头节点,首先获取头节点的下一个节点,将当前头节点的数据和节点指针置空,最后将链表的头节点属性改为下一个节点,size减1

(2)如果索引不为0,首先调用寻找节点的方法,获取到该节点的上一个节点,这样就可以拿到三个节点的信息

上一个节点(a) 当前节点(b) 下一个节点(c)

  1. 将a的节点指针,指向c

  2. 将b的数据和节点指针置空

  3. size减1

代码如下:

java 复制代码
//删除对应元素
    public void remove(int index) {
        //一:遍历链表找到对应节点和上一节点  (头节点的判断)
        //二:将上一节点的指向改为删除节点的指向
        //三:将连接的属性设置为null,size减1
        if (first != null && index == 0) {
            //1.获取头节点的下一节点,将头节点的属性清空
            //2.头节点重新赋值
            Node<E> next = first.next;
            first.next = null;
            first.data = null;
            first = next;
        } else {
            //上一个节点
            Node<E> prevNode = findNode(index - 1);
            //当前节点
            Node<E> node = prevNode.next;
            prevNode.next = node.next;
            node.next = null;
            node.data = null;
        }
        size--;
    }

节点数据删除:

对于节点数据删除来说,不能只删除一个节点,因为数据不是唯一的,可能多个节点的数据是相同的,这是一个注意的点,还有就是要考虑头节点删除的情况

思路:

(1)判断是否为头节点,如果删除的数据与头节点的数据相同的话,进行对应的逻辑:与索引删除的逻辑一样,只不过更改了判断条件

(2)不为头节点时,需要遍历整个链表,找到与删除数据一样的节点,并进行删除逻辑:

  1. 依旧需要从头节点开始遍历,因为头节点已经判断过了,所以去判断头节点的下一个节点,也就是node.next

  2. 如果是其数据为删除的数据,那么找到该节点的下一个节点(node.next.next),并将该节点置空(node.next),然后头节点更新节点指针,指向node.next.next,size减1

3.如果不为删除的数据,更新node节点,node=node.next;

代码如下:

java 复制代码
    public void removeValue(E data) {
        // 删除头节点的情况
        while (first != null && data.equals(first.data)) {
            Node<E> nextNode = first.next;
            first.next = null;
            first.data = null;
            first = nextNode;
            size--;
        }
        // 删除其他节点的情况
        Node<E> node = first;
        while (node != null && node.next != null) {
            if (data.equals(node.next.data)) {
                //当前节点的下一个节点的下一个节点
                Node<E> nextNode = node.next.next;
                node.next.data = null; // 释放节点数据
                node.next.next = null; // 释放节点指针
                node.next = nextNode; // 更新指向
                size--;
            }else {
                node = node.next;
            }
        }
    }

3.链表反转

链接:反转链表

这里需要理解的就是双指针的概念了,定义两个指针,a指针在b指针前,两个指针分别记录了相邻的节点,可以通过一个中间值temp,来更改两个节点的指向

下一个概念就是虚拟头,可以在头节点前想象一个空节点,data为null,next为头节点,在开始时,让a指针指向这个虚拟头,b指针指向头节点。

比如有 1 -> 2 -> 3,反转需要做的是 1 <- 2 <- 3

第一步:将2的next更改为1。 第二步:将3的next更改为2

指针的移动为:a指向1 ,b指向2 ---> a指向2,b指向3

换成节点为:node=first

nextNode = first.next

node.next = preNode上一个节点信息,初始为null

到这里反转完成了,下面需要移动指针

上一个指针指向就变成了当前节点

下一个指针就变成了当前节点的下一个节点

代码如下:

java 复制代码
    //链表反转
    public void reversal() {
        //定义,上一个节点,当前节点,下一个节点
        //从头节点开始
        Node<E> prevNode = null;
        Node<E> node = first;
        Node<E> nextNode = null;
        //遍历整个链表到尾部
        while (node != null){
            nextNode = node.next;
            node.next = prevNode;
            prevNode = node;
            node = nextNode;
        }
        first = prevNode;//当遍历到尾节点的时候,已经为空节点了,获取其上一个节点
    }

4.相交链表

链接:相交链表

简单来说,就是求两个链表交点节点的指针

我们求出两个链表的长度,并求出两个链表长度的差值,然后让curA移动到,和curB 末尾对齐的位置,这里不用纠结让A移动还是B移动,最终的结果两个链表的长度是一样的

此时我们就可以比较curA和curB是否相同,如果不相同,同时向后移动curA和curB,如果遇到curA == curB,则找到交点。

否则循环退出返回空指针。

代码如下:

java 复制代码
    //链表相交
    public Node<E> IntersectingLinkedList(Node<E> headA, Node<E> headB) {
        Node<E> curA = headA;
        Node<E> curB = headB;
        int lenA = 0;
        int lenB = 0;
        //计算两个链表的长度
        while (curA != null) {
            lenA++;
            curA = curA.next;
        }
        while (curB != null) {
            lenB++;
            curB = curB.next;
        }
        //计算差值
        int gap = lenA - lenB;
        if (gap > 0){
            //证明A为长链表
            for (int i = 0; i < gap; i++) {
                headA = headA.next;
            }
        }else {
            //证明B为长链表
            gap = lenB - lenA;
            for (int i = 0; i < gap; i++) {
                headB = headB.next;
            }
        }
        //无论A还是B链表,两个链表的长度是一样的,所以直接判断即可
        while (headA != null) {
            if (headA != headB){
                headA = headA.next;
                headB = headB.next;
            }
            //相等,返回
            else {
                return headA;
            }
        }
        return null;
    }

测试:

java 复制代码
public class TestMyLink {
    public static void main(String[] args) {
        // 创建链表
        MyLink<Integer> myLink = new MyLink<>();

        // 添加元素
        myLink.add(1);
        myLink.add(2);
        myLink.add(3);
        myLink.add(4);
        myLink.add(5);
        System.out.println("链表初始化后的元素:");
        System.out.println(myLink.printList());

        // 获取元素
        System.out.println("索引2处的元素:" + myLink.get(2)); // 应输出 3

        // 更新元素
        myLink.update(2, 10);
        System.out.println("更新索引2处的元素后的链表:");
        System.out.println(myLink.printList());

        // 在指定位置插入元素
        myLink.add(2, 7);
        System.out.println("在索引2处插入7后的链表:");
        System.out.println(myLink.printList());
        // 删除元素
        myLink.remove(0);
        System.out.println("删除索引0处的元素后的链表:");
        System.out.println(myLink.printList());

        // 头插法
        myLink.headAdd(0);
        System.out.println("头插法插入0后的链表:");
        System.out.println(myLink.printList());

        // 链表反转
        myLink.reversal();
        System.out.println("链表反转后的元素:");
        System.out.println(myLink.printList());

        // 删除元素2
        myLink.removeValue(2);

        // 打印链表
        System.out.println("删除元素2后的链表内容:");
        System.out.println(myLink.printList()); // 应该输出: "1 3 4"
        System.out.println(4/2);
    }



}

结果:

成功!还有一些我就不详细去说了,思路都是一样的,大家可以看我之前的手写单向链表,这篇更多也是复习,我自己再写一遍了,今天就到这里了!

完整代码:

java 复制代码
public class MyLink<E> {
    //单向链表:链表的长度,节点,头节点
    private int size;
    //头节点
    Node<E> first;

    //尾插法,新增元素
    public void add(E data) {
        Node<E> node = new Node<>(data, null);
        //首先判断头节点是否为空,如果为空则证明为空链表,第一次添加的节点为头节点
        if (first == null) {
            first = node;
            size++;
            return;
        }
        //找到链表的尾部
        Node<E> endNode = findEnd();
        endNode.next = node;
        size++;
    }

    //寻找链表的尾部节点,节点内的指向下一个节点的属性为null,则为尾部节点
    private Node<E> findEnd() {
        //从头节点开始遍历
        Node<E> node = first;
        while (node.next != null) {
            node = node.next;
        }
        return node;
    }

    //删除对应元素
    public void remove(int index) {
        //一:遍历链表找到对应节点和上一节点  (头节点的判断)
        //二:将上一节点的指向改为删除节点的指向
        //三:将连接的属性设置为null,size减1
        if (index == 0) {
            //1.获取头节点的下一节点,将头节点的属性清空
            //2.头节点重新赋值
            Node<E> next = first.next;
            first.next = null;
            first.data = null;
            first = next;
        } else {
            Node<E> prevNode = findNode(index - 1);
            Node<E> node = prevNode.next;

            prevNode.next = node.next;

            node.data = null;
            node.next = null;
        }
        size--;
    }

    public void removeValue(E data) {
        // 删除头节点的情况
        while (first != null && data.equals(first.data)) {
            Node<E> nextNode = first.next;
            first.next = null;
            first.data = null;
            first = nextNode;
            size--;
        }

        // 删除其他节点的情况
        Node<E> node = first;
        while (node != null && node.next != null) {
            if (data.equals(node.next.data)) {
                // 删除节点
                Node<E> nextNode = node.next.next;
                node.next.data = null; // 释放节点数据
                node.next.next = null; // 释放节点指针
                node.next = nextNode; // 更新指向
                size--;
            } else {
                node = node.next;
            }
        }
    }

    //寻找对于索引处的节点
    public Node<E> findNode(int index) {
        Node<E> node = first;
        for (int i = 0; i < index; i++) {
            node = node.next;
        }
        return node;
    }

    //获取对应索引处的数据
    public E get(int index) {
        Node<E> node = findNode(index);
        return node.data;
    }

    //修改对应索引处的数据
    public void update(int index, E data) {
        Node<E> node = findNode(index);
        node.data = data;
    }

    //向指定位置添加一个元素
    public void add(int index, E data) {
        // 检查索引是否有效
        if (index < 0 || index > size) {
            System.out.println("索引越界");
            return;
        }

        Node<E> node = new Node<>(data, null);

        if (index == 0) {
            // 头插法
            headAdd(data);
        } else if (index == size) {
            // 尾插法
            add(data); // 使用已有的尾插法
        } else {
            // 插入到指定位置
            Node<E> prevNode = findNode(index - 1);
            Node<E> nextNode = prevNode.next;

            prevNode.next = node;
            node.next = nextNode;

            size++;
        }
    }

    //头插法
    public void headAdd(E data) {
        //获取头节点,新节点指向头节点,并修改头节点变量
        Node<E> node = new Node<>(data, null);
        node.next = first;
        first = node;
        size++;
    }

    //链表反转
    public void reversal() {
        //定义,上一个节点,当前节点,下一个节点
        //从头节点开始
        Node<E> prevNode = null;
        Node<E> node = first;
        Node<E> nextNode = null;
        //遍历到链表的尾部
        while (node != null) {
            //保存当前节点的指向
            nextNode = node.next;

            //开始反转
            node.next = prevNode;//指向上一节点
            prevNode = node;//将当前节点变为上一个节点
            node = nextNode;//移动节点
        }

        first = prevNode;//当遍历到尾节点的时候,已经为空节点了
    }

    public String printList() {
        Node<E> node = first;
        StringBuilder list = new StringBuilder();
        while (node != null) {
            list.append(node.data);
            list.append(" ");
            node = node.next;
        }
        return list.toString();
    }

    //链表相交
    public Node<E> IntersectingLinkedList(Node<E> headA, Node<E> headB) {
        Node<E> curA = headA;
        Node<E> curB = headB;
        int lenA = 0;
        int lenB = 0;
        //计算两个链表的长度
        while (curA != null) {
            lenA++;
            curA = curA.next;
        }
        while (curB != null) {
            lenB++;
            curB = curB.next;
        }
        //计算差值
        int gap = lenA - lenB;
        if (gap > 0){
            //证明A为长链表
            for (int i = 0; i < gap; i++) {
                headA = headA.next;
            }
        }else {
            //证明B为长链表
            gap = lenB - lenA;
            for (int i = 0; i < gap; i++) {
                headB = headB.next;
            }
        }
        //无论A还是B链表,两个链表的长度是一样的,所以直接判断即可
        while (headA != null) {
            if (headA != headB){
                headA = headA.next;
                headB = headB.next;
            }
            //相等,返回
            else {
                return headA;
            }
        }
        return null;
    }

}
相关推荐
爬菜1 分钟前
异常(5)
java
闻缺陷则喜何志丹5 分钟前
【二分查找、滑动窗口】P10389 [蓝桥杯 2024 省 A] 成绩统计|普及+
c++·算法·蓝桥杯·二分查找·滑动窗口·洛谷·成绩
苹果酱056725 分钟前
Golang的数据库备份与恢复
java·vue.js·spring boot·mysql·课程设计
青石路44 分钟前
经由同个文件多次压缩的文件MD5都不一样问题排查,感慨AI的强大!
java·后端
木头没有瓜1 小时前
Mybatis集合嵌套查询,三级嵌套
java·tomcat·mybatis
知行021 小时前
23中设计模式之观察者模式
java·观察者模式·设计模式
迷路的小犀牛1 小时前
JAVA编程【设计模式之工厂模式】
java·开发语言·设计模式
乔冠宇1 小时前
蓝桥杯算法——铠甲合体
算法·职场和发展·蓝桥杯
郑祎亦1 小时前
【JAVA面试题】Spring、Spring MVC、Spring Boot、Spring Cloud的区别与联系
java·spring·mvc