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;
    }

}
相关推荐
刚学HTML3 分钟前
leetcode 05 回文字符串
算法·leetcode
Yan.love17 分钟前
开发场景中Java 集合的最佳选择
java·数据结构·链表
椰椰椰耶20 分钟前
【文档搜索引擎】搜索模块的完整实现
java·搜索引擎
大G哥20 分钟前
java提高正则处理效率
java·开发语言
AC使者23 分钟前
#B1630. 数字走向4
算法
冠位观测者27 分钟前
【Leetcode 每日一题】2545. 根据第 K 场考试的分数排序
数据结构·算法·leetcode
智慧老师1 小时前
Spring基础分析13-Spring Security框架
java·后端·spring
lxyzcm1 小时前
C++23新特性解析:[[assume]]属性
java·c++·spring boot·c++23
古希腊掌管学习的神1 小时前
[搜广推]王树森推荐系统笔记——曝光过滤 & Bloom Filter
算法·推荐算法
qystca1 小时前
洛谷 P1706 全排列问题 C语言
算法