【数据结构】 链表

概述

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

链表的定义:

平时在刷题的时候,链表的节点都默认定义好了,直接用就行了,所以很多人都没有注意到链表的节点是如何定义的。
定义链表节点的方式

class Node {
    int val; //数据:节点上存储的元素
    Node next; //指针域:指向下一个节点的指针
    /*
    节点的三个构造函数
    */
    public Node(){} 
    public Node(int val) {
        this.val=val;
    }
    public Node(int val, Node next) {
        this.val = val;
        this.next = next;
    }
}

链表的存储方式:
数组是在内存中是连续分布的,但是链表在内存中可不是连续分布的。

链表是通过指针域的指针链接在内存中各个节点。

所以链表中的节点在内存中不是连续分布的 ,而是散乱分布在内存中的某地址上,分配机制取决于操作系统的内存管理。

操作链表的注意点:
在对链表进行操作时,有两种方式:
1.直接使用原来的链表进行相关操作
2.对原链表设置一个虚拟的头节点,并指向原链表的头节点,再进行相关操作

有何区别?
以删除链表中节点的操作为例

移除头节点和移除链表中其他节点的操作时不一样的,其它节点的移除都是通过移除前一个节点来达到移除当前节点的效果,node.next == null,然而头节点没有前一个节点,所以需要将头结点向后移动一位 head = head.next,这样在写代码的时候,就需要单独写一段逻辑来处理头节点的情况,这样就是的我们的代码书写看起来十分冗余,不美观。

那么我们就需要以一种统一的逻辑来移除链表中的所有节点。
所以 设置一个虚拟头节点,就可以将原链表中的所有节点都按照统一的方式来进行移除了。

设置虚拟头节点:

Node temp = new Node(0);
temp.mext = head;

链表的相关操作

删除节点

203.移除链表元素

给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足
Node.val == val 的节点,并返回新的头节点。 

在这里插入图片描述
思路:
创建虚拟头节点,指向头节点,指针依次后移,如果 节点的元素值 == val, 释放该节,释放节点前,要将前一个节点的指针指向该删除节点的后继接节点。

代码

/**
 * 节点的定义
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode removeElements(ListNode head, int val) {
        ListNode preHead = new ListNode(0);
        preHead.next = head;
        ListNode temp = preHead;

        while(temp.next != null){
            if(temp.next.val == val){
                temp.next = temp.next.next;
            } else{
                temp = temp.next;
            }
        }

        return preHead.next;
    }
}

19.删除链表的倒数第N个节点

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

在这里插入图片描述

解法一:
先遍历链表获取链表的长度size,再通过遍历size - n,来到需要删除节点的前一个节点,然后通过

temp.next = temp.next.next,来删除该节点。

代码:

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
 
        ListNode node = new ListNode(0,head); // 虚拟头节点

        int size = getLength(head); //获取链表的长度

        ListNode temp = node;
        
        //来到待删除节点处
        for(int i = 0; i < size - n; i++){
            temp = temp.next;
        }
        
        temp.next = temp.next.next; //删除节点

        return node.next;
    }

	/*
	遍历链表,获取链表的长度
	*/
    public int getLength(ListNode head){
        int length = 0;
        while(head != null){
            ++length;
            head = head.next;
        }

        return length;
    }
}

解法二:
进阶:快慢双指针

双指针的经典应用,如果要删除倒数第n个节点,让fast移动n步,然后让fast和slow同时移动,直到

fast指向链表末尾。删掉slow所指向的节点就可以了。

步骤:
1.定义fast指针和slow指针,初始值为虚拟头结点。
2.fast首先走n 步 ,为什么是n呢,因为只有这样同时移动的时候slow才能指向删除节点的上一个节点(方便做删除操作)。
3.fast和slow同时移动,直到fast指向末尾。
4.删除slow指向的下一个节点。

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummy = new ListNode(-1);
        dummy.next = head;

        ListNode slow = dummy;
        ListNode fast = dummy;
		
		// 先让fast指针移动n步
        while (n-- > 0) {
            fast = fast.next;
        }
        
        // 让fast指针和slow指针一起遍历
        while (fast.next != null) {
            slow = slow.next;
            fast = fast.next;
        }
        
        slow.next = slow.next.next;

        return dummy.next;
    }
}

反转链表

206.反转链表

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表

在这里插入图片描述
题解:
定义三个节点指针,cur指针指向头节点,pre指针指向cur指针的前一个节点,temp指针用来保存cur指针的后一个节点。

在while循环中,首先将cur.next用temp进行保存,接下来改变 cur.next 的指向了,将cur.next 指向pre ,此时已经反转了第一个节点了。然后更新pre和cur。

循环中继续移动pre和cur指针。

最后,cur 指针已经指向了null,循环结束,链表也反转完毕了。 此时我们return pre指针就可以了,pre指针就指向了新的头结点。

class Solution {
    public ListNode reverseList(ListNode head) {

        ListNode cur = head;
        ListNode pre = null;
        ListNode temp = null; // 保存cur的下一个节点

        while(cur != null){
            temp = cur.next; // 保存一下 cur的下一个节点,因为接下来要改变cur->next
            cur.next = pre; // 翻转操作
            // 更新pre 和 cur指针
            pre = cur;
            cur = temp;
        }
        return pre;
    }
}

链表相交

面试题 02.07. 链表相交

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。

图示两个链表在节点 c1 开始相交:

在这里插入图片描述


题目数据 保证 整个链式结构中不存在环。

注意,函数返回结果后,链表必须 保持其原始结构 。

在这里插入图片描述

思路:
1.先到达每个链表的最后一个节点,并记录长度(这里不记录各自的长度,而是记录两个链表之间的差值)。

2.再比较它们的end节点的内存地址是否一样,不一样则一定不相交。

3.相交的话,用node1作为最长链表的头节点,移动==差值的步数。

4.node1和node2同时移动,返回直到相等的节点。

代码:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        
        ListNode node1 = headA;
        ListNode node2 = headB;
        int n = 0;

        while(node1 != null){
            n++;
            node1 = node1.next;
        }

        while(node2 != null){
            n--;
            node2 = node2.next;
        }

        if(node1 != node2){
            return null;
        }

        node1 = n > 0 ? headA : headB;
        node2 = node1 == headA ? headB : headA;
        n = Math.abs(n);

        while(n-- > 0){
            node1 = node1.next;
        }

        while(node1 != node2){
            node1 = node1.next;
            node2 = node2.next;
        }

        return node1;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值