代码随想录|第二章、链表

day 3 链表1

链表的基础理论

1.链表的定义|Java(代码随想录补充笔记)

在Java中,链表是一种常见的数据结构,用于存储和操作一系列的元素。链表由节点组成,每个节点包含一个数据元素和一个指向下一个节点的引用。在Java中,可以使用自定义类来定义链表。以下是一个简单的链表节点类的示例:

class ListNode {
    int val;
    ListNode next;

    public ListNode(int val) {
        this.val = val;
        this.next = null;
    }
}

在上面的示例中,ListNode类表示链表的一个节点。它包含一个整型的数据元素val和一个指向下一个节点的引用next。构造函数用于初始化节点的值和next引用。

要创建一个链表,可以通过创建节点对象并将它们连接起来来实现。例如,以下是一个创建链表的示例:

ListNode head = new ListNode(1);
ListNode second = new ListNode(2);
ListNode third = new ListNode(3);

head.next = second;
second.next = third;

在上面的示例中,我们创建了一个包含三个节点的链表,节点的值分别为1、2和3。通过将节点的next引用连接起来,我们形成了一个链表的结构。

public class ListNode {
    // 结点的值
    int val;
    // 下一个结点
    ListNode next;
    // 节点的构造函数(无参)
    public ListNode() {
    }
    // 节点的构造函数(有一个参数)
    public ListNode(int val) {
        this.val = val;
    }
    // 节点的构造函数(有两个参数)
    public ListNode(int val, ListNode next) {
        this.val = val;
        this.next = next;
    }
}

【1】203移除链表元素

建议: 本题最关键是要理解 虚拟头结点的使用技巧,这个对链表题目很重要。
class Solution {
    public ListNode removeElements(ListNode head, int val) { 
    if (head == null) {
        return head;
    }
    // 创建一个dummy节点,将其指向头节点
    ListNode dummy = new ListNode(-1, head);
    // pre指针用于跟踪上一个节点
    ListNode pre = dummy;
    // cur指针用于遍历当前节点
    ListNode cur = head;
    // 遍历链表
    while (cur != null) {
        // 如果当前节点的值等于给定值,删除当前节点
        if (cur.val == val) {
            pre.next = cur.next;
            cur = cur.next;
        } 

        // 如果当前节点的值不等于给定值,更新pre指针为当前节点
        else {
            
            pre = cur;
            cur = cur.next;
        }
        
    }
    // 返回dummy节点的下一个节点作为新的头节点
    return dummy.next;
}
}

【2】707.设计链表  

建议: 这是一道考察 链表综合操作的题目,不算容易,可以练一练 使用虚拟头结点

注意:

1.首先记得合法性判断;

2.第n个节点一定时cur.next;

3.插入节点时,注意指针变动的现后顺序。

class MyLinkedList { 
    ListNode head; // 虚拟头节点
    int size;

    //0.MyLinkedList() 初始化 MyLinkedList 对象
    public MyLinkedList() {
        head = new ListNode(0); // 创建虚拟头节点
        size = 0;
    }


    //1. 获取链表中下标为 index 的节点的值。如果下标无效,则返回 -1 。
    public int get(int index) {
        //判断index的合法性
        if (index < 0 || index >= size) {
            return -1; // 索引越界,返回-1
        }
        ListNode curr = head.next; // 从第一个真实节点开始遍历
        for (int i = 0; i < index; i++) {
            curr = curr.next;
        }
        return curr.val;
    }

    //2.在链表头部添加节点
    public void addAtHead(int val) {
        ListNode newNode = new ListNode(val);
        newNode.next = head.next; // 将新节点的下一个节点指向原来的第一个节点
        head.next = newNode; // 将头节点的下一个节点指向新节点
        size++;
    }
    
    //3.在链表尾部添加节点
    public void addAtTail(int val) {
        ListNode newNode = new ListNode(val);
        ListNode curr = head;
        while (curr.next != null) {
            curr = curr.next;
        }
        curr.next = newNode; // 将最后一个节点的下一个节点指向新节点
        size++;

    }
    
    //4.在指定索引处添加节点
    public void addAtIndex(int index, int val) {
        if (index < 0 || index > size) {
            return; // 索引越界,不进行操作
        }
        ListNode newNode = new ListNode(val);
        ListNode curr = head;
        for (int i = 0; i < index; i++) {
            curr = curr.next;
        }
        newNode.next = curr.next; // 将新节点的下一个节点指向当前节点的下一个节点
        curr.next = newNode; // 将当前节点的下一个节点指向新节点
        size++;

    }
    
    // 5.删除指定索引处的节点
    public void deleteAtIndex(int index) {
          if (index < 0 || index >= size) {
            return; // 索引越界,不进行操作
        }
        ListNode curr = head;
        for (int i = 0; i < index; i++) {
            curr = curr.next;
        }
        curr.next = curr.next.next; // 将当前节点的下一个节点指向下下个节点,跳过要删除的节点
        size--;
    }

    }

【3】206.反转链表

class Solution {
    public ListNode reverseList(ListNode head) {
        // 初始化三个指针变量
        ListNode cur = head; // 当前节点,用于遍历链表
        ListNode temp = null; // 临时节点,用于保存当前节点的下一个节点
        ListNode pre = null; // 反转后链表的头节点

        // 遍历链表,反转节点指针
        while (cur != null) {
            // 保存当前节点的下一个节点
            temp = cur.next;
            // 将当前节点的next指针指向反转后链表的头节点
            cur.next = pre;
            // 更新pre为当前节点,cur为下一个节点,继续遍历
            pre = cur;
            cur = temp;
        }
        
        // 返回反转后的链表头节点
        return pre;
    }
}

day4 链表2

【4】24. 两两交换链表中的节点

class Solution {
    public ListNode swapPairs(ListNode head) {
        ListNode dummy=new ListNode(-1); //定义一个新的虚拟头节点
        dummy.next=head;//虚拟头节点的next是head
        //定义4个临时节点,cur,fist,second,temp;
        ListNode cur=dummy;//初始化cur指向虚拟头指针,要交换的两个节点的前一个节点
        ListNode first;//临时节点,交换的两个节点的第一个节点
        ListNode second;//临时节点,交换的两个节点的第二个节点
        ListNode temp;//临时节点。交换的两个节点的后一个节点
        while(cur.next!=null&&cur.next.next!=null){//考虑奇数个和偶数个节点的情况
            first=cur.next;
            second=cur.next.next;
            temp=cur.next.next.next;

            cur.next=second;//步骤一
            second.next=first;//步骤二
            first.next=temp;//步骤二
            cur=first;//易错点,因为first和second交换了,所以第二轮cur指向first
        }
        return dummy.next;//易错点 不能返回head有可能会出错,因为head有可能是空的。
    }
}

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

//关键:找到第n个节点
//虚拟头节点
//操作指针要在删除指针节点的前一步
//快慢指针
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummy=new ListNode(-1);
        dummy.next=head;

        ListNode fast=dummy;
        ListNode slow=dummy;
        
        //快指针先走n步,保证快慢指针相差n个节点
        for(int i=0;i<n;i++){//注意这里!!!
            fast=fast.next;
        }
        //当fast的next为空时,slow在要删除的节点的前一个节点
        while(fast.next!=null){//注意这里。是fast.next!=null,而不是fast!=null
            fast=fast.next;
            slow=slow.next;
        }
        slow.next=slow.next.next;
        return dummy.next;
    }
}

【6】面试题 02.07. 链表相交

//简单题
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode curA = headA;
        ListNode curB = headB;
        int lenA = 0, lenB = 0;
        while (curA != null) { // 求链表A的长度
            lenA++;
            curA = curA.next;
        }
        while (curB != null) { // 求链表B的长度
            lenB++;
            curB = curB.next;
        }
        curA = headA;
        curB = headB;
        // 让curA为最长链表的头,lenA为其长度
        if (lenB > lenA) {
            //1. swap (lenA, lenB);
            int tmpLen = lenA;
            lenA = lenB;
            lenB = tmpLen;
            //2. swap (curA, curB);
            ListNode tmpNode = curA;
            curA = curB;
            curB = tmpNode;
        }
        // 求长度差
        int gap = lenA - lenB;
        // 让curA和curB在同一起点上(末尾位置对齐)
        while (gap-- > 0) {
            curA = curA.next;
        }
        // 遍历curA 和 curB,遇到相同则直接返回
        while (curA != null) {
            if (curA == curB) {
                return curA;
            }
            curA = curA.next;
            curB = curB.next;
        }
        return null;
    }

}

【7】142.环形链表II(难题)

public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode slow = head; // 慢指针,每次移动一步
        ListNode fast = head; // 快指针,每次移动两步
        while (fast != null && fast.next != null) {
            slow = slow.next; // 慢指针每次移动一步
            fast = fast.next.next; // 快指针每次移动两步
            if (slow == fast) { // 如果快慢指针相遇,说明有环
                ListNode index1 = fast; // 创建一个指针index1,指向相遇节点
                ListNode index2 = head; // 创建一个指针index2,指向链表头结点
                // 两个指针从不同的起点开始,每次移动一步,直到相遇,相遇点即为环的入口节点
                while (index1 != index2) {
                    index1 = index1.next;
                    index2 = index2.next;
                }
                return index1; // 返回环的入口节点
            }
        }
        return null; // 如果没有环,返回null
    }
}

这段代码使用快慢指针的思想来判断链表是否有环,并找到环的入口节点。首先,我们初始化两个指针slowfast,它们都指向链表的头结点。然后,我们使用一个循环来移动指针,每次慢指针移动一步,快指针移动两步。如果链表有环,那么快慢指针一定会相遇。

当快慢指针相遇时,我们创建两个新的指针index1index2,分别指向相遇节点和链表的头结点。然后,我们使用两个指针以相同的速度移动,每次移动一步,直到它们相遇。相遇点即为环的入口节点。

最后,我们返回环的入口节点。如果链表没有环,那么快指针会先到达链表的末尾,此时我们返回null。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值