数据结构-链表笔记

旋转链表

61. 旋转链表 - 力扣(LeetCode)

/**
 * Definition for singly-linked list.
 * 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 rotateRight(ListNode head, int k) {
        // 如果头结点为空,直接返回null
        if(head == null){
            return null;
        }
        // 创建一个哑节点,其next指向头结点
        ListNode dummy = new ListNode();
        dummy.next = head;
        // nextNode用于遍历链表
        ListNode nextNode = dummy;

        // temp用于计算链表的长度
        ListNode temp = head;
        // 初始化链表长度为1
        int len = 1;
        // 遍历链表,计算链表长度
        while(temp.next != null){
            len++;
            temp = temp.next;
        }

        // 重置temp为头结点
        temp = head;
        // 移动temp到倒数第k+1个节点
        for(int i = 0 ; i < len - (k % len) - 1 ; i++){
            temp = temp.next;
        }

        // nextNode的next指向temp的next,即新的头结点
        nextNode.next = temp.next;
        // temp的next置为null,断开链表
        temp.next = null;
        // 移动nextNode到新的尾结点
        while(nextNode.next != null){
            nextNode = nextNode.next;
        }
        // 将新的尾结点的next指向原来的头结点,形成新的链表
        nextNode.next = head;
        // 返回新的头结点
        return dummy.next;
    }
}

移除节点

203. 移除链表元素 - 力扣(LeetCode)

/**
 * Definition for singly-linked list.
 * 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 dummyHead = new ListNode();
        dummyHead.next = head;

        // 初始化双指针
        ListNode pre = dummyHead; // pre指向当前节点的前一个节点
        ListNode cur = head;      // cur指向当前节点

        // 遍历链表
        while (cur != null) {
            // 如果当前节点的值等于要删除的值
            if (cur.val == val) {
                // 跳过当前节点,直接链接前一个节点到当前节点的下一个节点
                pre.next = cur.next;
            } else {
                // 如果当前节点的值不等于要删除的值,更新 pre 指针为当前节点
                pre = cur;
            }
            // 无论当前节点的值是否被删除,都移动 cur 指针到下一个节点
            cur = cur.next;
        }

        // 返回新的头节点,跳过虚拟头节点
        return dummyHead.next;
    }
}

82. 删除排序链表中的重复元素 II - 力扣(LeetCode)

/**
 * Definition for singly-linked list.
 * 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 {
    /**
     * 删除排序链表中的重复元素
     * @param head 链表的头节点
     * @return 删除重复元素后的链表头节点
     */
    public ListNode deleteDuplicates(ListNode head) {
        // 如果头节点为空,直接返回空
        if(head == null){
            return head;
        }

        // 创建一个哑节点(dummy node),它的next指向头节点
        // 这样可以简化在循环中删除头节点的复杂性
        ListNode dummpHead = new ListNode(-1);
        dummpHead.next = head;

        // 从哑节点开始遍历链表
        ListNode cur = dummpHead;

        // 循环直到当前节点的下一个和下下一个节点都为空
        while(cur.next != null && cur.next.next != null) {
            // 如果当前节点的值和下一个节点的值相同
            if(cur.next.val == cur.next.next.val){
                int val = cur.next.val;

                // 循环删除所有值相同的节点
                while(cur.next != null && cur.next.val == val){
                    // 将当前节点的next指向下一个节点的next,从而删除下一个节点
                    cur.next = cur.next.next;
                }
            }else{
                // 如果当前节点的值和下一个节点的值不同,移动到下一个节点
                cur = cur.next;
            }
        }

        // 返回哑节点的下一个节点,即删除重复元素后的链表头节点
        return dummpHead.next;            
        
    }
}

设计链表

在链表类中实现这些功能:得到第 index 个节点的值,

在链表的第一个元素之前添加一个值为 val 的节点,

将值为 val 的节点追加到链表的最后一个元素,

在链表中的第 index 个节点之前添加值为 val  的节点,

删除链表中的第 index 个节点

707. 设计链表 - 力扣(LeetCode)

解法:单链表

class ListNode {
    int val; // 节点的值
    ListNode next; // 指向下一个节点的指针
    
    ListNode() {} // 默认构造函数
    ListNode(int val) { // 带值的构造函数
        this.val = val;
    }
}

class MyLinkedList {
    // size 存储链表元素的个数
    int size;
    // 虚拟头结点,便于处理头节点的插入和删除
    ListNode dummyHead;

    public MyLinkedList() {
        size = 0; // 初始化链表大小为 0
        dummyHead = new ListNode(0); // 创建虚拟头结点
    }
    
    // 获取链表中第 index 个节点的值,索引从 0 开始
    public int get(int index) {
        // 检查索引是否有效
        if (index < 0 || index >= size) {
            return -1; // 返回 -1 表示索引无效
        }

        ListNode curNode = dummyHead; // 从虚拟头节点开始
        // 遍历到目标索引的节点
        for (int i = 0; i <= index; i++) {
            curNode = curNode.next; // 移动到下一个节点
        }

        return curNode.val; // 返回目标节点的值
    }
    
    // 在链表头部添加一个新节点
    public void addAtHead(int val) {
        ListNode newNode = new ListNode(val); // 创建新节点
        newNode.next = dummyHead.next; // 新节点指向当前头节点
        dummyHead.next = newNode; // 虚拟头节点指向新节点
        size++; // 更新链表大小
    }
    
    // 在链表尾部添加一个新节点
    public void addAtTail(int val) {
        ListNode curNode = dummyHead; // 从虚拟头节点开始
        // 遍历到链表尾部
        while (curNode.next != null) {
            curNode = curNode.next; // 移动到下一个节点
        }
        ListNode newNode = new ListNode(val); // 创建新节点
        curNode.next = newNode; // 尾节点指向新节点
        size++; // 更新链表大小
    }
    
    // 在指定索引处添加一个新节点
    public void addAtIndex(int index, int val) {
        // 如果索引大于链表大小,无法添加
        if (index > size) {
            return;
        }
        // 如果索引小于 0,从头部插入
        if (index < 0) {
            index = 0;
        }
        ListNode newNode = new ListNode(val); // 创建新节点
        ListNode curNode = dummyHead; // 从虚拟头节点开始
        // 遍历到目标索引的前一个节点
        for (int i = 0; i < index; i++) {
            curNode = curNode.next; // 移动到下一个节点
        }
        // 插入新节点
        newNode.next = curNode.next; // 新节点指向当前位置的下一个节点
        curNode.next = newNode; // 前一个节点指向新节点
        size++; // 更新链表大小
    }
    
    // 删除指定索引的节点
    public void deleteAtIndex(int index) {
        // 检查索引是否有效
        if (index < 0 || index >= size) {
            return; // 索引无效,不进行任何操作
        }
        size--; // 先减少链表大小
        ListNode curNode = dummyHead; // 从虚拟头节点开始
        // 遍历到目标索引的前一个节点
        for (int i = 0; i < index; i++) {
            curNode = curNode.next; // 移动到下一个节点
        }
        // 删除目标节点
        curNode.next = curNode.next.next; // 前一个节点指向目标节点的下一个节点
    }
}

/**
 * Your MyLinkedList object will be instantiated and called as such:
 * MyLinkedList obj = new MyLinkedList();
 * int param_1 = obj.get(index);
 * obj.addAtHead(val);
 * obj.addAtTail(val);
 * obj.addAtIndex(index,val);
 * obj.deleteAtIndex(index);
 */

双指针

合并两个有序链表

/**
 * Definition for singly-linked list.
 * 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 mergeTwoLists(ListNode list1, ListNode list2) {
        // 临时指针,指向链表1的头节点
        ListNode list1Temp = list1;
        // 临时指针,指向链表2的头节点
        ListNode list2Temp = list2;

        // 创建一个哑节点,作为合并后的链表的头节点的前一个节点
        ListNode dummyHead = new ListNode(-1, null);
        // 临时指针,指向合并后的链表的当前节点
        ListNode temp = dummyHead;

        // 当链表1和链表2都还有节点时,进行比较和合并
        while(list1Temp != null && list2Temp != null){
            // 初始化较小值为整数最大值
            int small = Integer.MAX_VALUE;
            // 如果链表1当前节点的值大于等于链表2当前节点的值
            if(list1Temp.val >= list2Temp.val){
                // 更新较小值为链表2当前节点的值
                small = list2Temp.val;
                // 链表2的临时指针后移
                list2Temp = list2Temp.next;
            }else if(list2Temp.val > list1Temp.val){
                // 更新较小值为链表1当前节点的值
                small = list1Temp.val;
                // 链表1的临时指针后移
                list1Temp = list1Temp.next;
            }
            // 创建一个新节点,值为较小值
            ListNode newNode = new ListNode(small);
            // 将新节点连接到合并后的链表的当前节点后面
            temp.next = newNode;
            // 合并后的链表的临时指针后移
            temp = temp.next;
        }

        // 如果链表1还有剩余节点,则将其连接到合并后的链表后面
        // 如果链表2还有剩余节点,则将其连接到合并后的链表后面
        temp.next = list1Temp == null ? list2Temp : list1Temp;

        // 返回合并后的链表的头节点
        return dummyHead.next;
    }
}

反转链表  (难)

206. 反转链表 - 力扣(LeetCode)

class Solution {
    // 反转链表的核心方法,传入链表的头节点 head
    public ListNode reverseList(ListNode head) {
        // 如果链表为空或链表只有一个节点,则不需要反转,直接返回原链表的头节点
        if (head == null || head.next == null) {
            return head;  // 终止条件:空链表或只有一个节点
        }

        // 初始化两个指针:pre 用来跟踪已经反转的部分,cur 用来跟踪未反转的部分
        ListNode pre = head;   // pre 指向当前节点,初始时为链表的头节点
        ListNode cur = head.next;   // cur 指向下一个节点,初始时为头节点的下一个节点

        // 将头节点的 next 设为 null,因为反转后原来的头节点会成为尾节点,尾节点的 next 应该是 null
        head.next = null;

        // 开始遍历链表,直到 cur 为空(即链表末尾)
        while (cur != null) {
            ListNode temp = cur.next;   // 暂存 cur 的下一个节点,防止链表断开后丢失后续节点
            cur.next = pre;   // 将当前节点 cur 的 next 指向 pre,从而实现反转
            pre = cur;   // pre 前进到 cur,表示反转后的部分已经包括当前节点
            cur = temp;   // cur 前进到下一个节点(即之前暂存的节点),继续反转
        }

        // 当 cur 为 null 时,pre 指向的是反转后的新头节点,返回该节点
        return pre;
    }
}

两两交换链表中的节点

24. 两两交换链表中的节点 - 力扣(LeetCode)

// 定义链表节点类
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 swapPairs(ListNode head) {
        // 定义一个虚拟头节点 dummyHead,用来简化链表头部的操作
        ListNode dummyHead = new ListNode(0);  // 虚拟头节点,值为0,指向实际链表的头节点
        dummyHead.next = head;  // 将虚拟头的 next 指向传入的 head,便于处理链表的开头

        // 定义临时指针 temp,用来遍历链表。初始指向 dummyHead,方便处理链表头部
        ListNode temp = dummyHead;
        
        // node1 和 node2 分别用于指向要交换的两个相邻节点
        ListNode node1;
        ListNode node2;

        // 只要 temp 后面有两个节点存在(即 temp.next 和 temp.next.next 都不为空),就可以继续交换
        while (temp.next != null && temp.next.next != null) {
            // node1 指向第一对中第一个节点
            node1 = temp.next;
            // node2 指向第一对中第二个节点
            node2 = temp.next.next;

            // 开始交换:将 temp 的 next 指向第二个节点(node2)
            temp.next = node2;
            // 将第一个节点 node1 的 next 指向第二个节点 node2 的下一个节点,即交换后第一个节点应该指向的节点
            node1.next = node2.next;
            // 将第二个节点 node2 的 next 指向第一个节点 node1,完成交换
            node2.next = node1;

            // 将 temp 指向 node1,继续处理下一对
            temp = node1;  // node1 是交换后的第二个节点,所以 temp 移动到这里准备处理下一对节点
        }

        // 返回新链表的头节点,即 dummyHead 的 next
        return dummyHead.next;  // dummyHead 是虚拟头节点,实际链表的头节点在 dummyHead.next
    }
}

删除链表的倒数第N个节点(难)

19. 删除链表的倒数第 N 个结点 - 力扣(LeetCode)

// 定义链表节点类
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 {
    // 移除链表中倒数第 n 个节点
    public ListNode removeNthFromEnd(ListNode head, int n) {
        // 边界检查,如果链表为空,直接返回 null
        if (head == null) {
            return null;
        }

        // 定义一个虚拟头节点,dummyHead 用来处理链表头部的特殊情况(例如删除头节点)
        ListNode dummyHead = new ListNode(0);
        dummyHead.next = head; // dummyHead.next 指向链表头节点

        // 定义快指针 fast 和慢指针 slow,都初始化为 dummyHead
        ListNode fast = dummyHead;
        ListNode slow = dummyHead;

        // 快指针 fast 先前进 n+1 步,以便与慢指针 slow 之间的距离为 n
        for (int i = 0; i <= n; i++) {
            fast = fast.next;
        }

        // 快慢指针同时向前移动,直到 fast 到达链表末尾
        while (fast != null) {
            fast = fast.next;
            slow = slow.next;
        }

        // 此时慢指针 slow 指向待删除节点的前一个节点,执行删除操作
        slow.next = slow.next.next;

        // 返回新的链表头节点(dummyHead.next),此时 dummyHead.next 是链表的头节点
        return dummyHead.next;
    }
}

训练计划 IV

/**
 * Definition for singly-linked list.
 * 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 {
    /**
     * 合并两个排序的链表。
     * @param l1 第一个链表的头节点。
     * @param l2 第二个链表的头节点。
     * @return 合并后的链表头节点。
     */
    public ListNode trainningPlan(ListNode l1, ListNode l2) {
        // 创建一个哑节点,作为合并后链表的头节点的前驱节点。
        ListNode dummpHead = new ListNode(-1);

        // cur指针用于追踪合并后链表的最后一个节点。
        ListNode cur = dummpHead;

        // 当两个链表都未到达末尾时,进行循环。
        while(l1 != null && l2 != null){
            // 如果l1的当前节点值小于l2的当前节点值,将l1的当前节点接到cur后面。
            if(l1.val < l2.val){
                cur.next = l1;
                l1 = l1.next;
            } else {
                // 否则,将l2的当前节点接到cur后面。
                cur.next = l2;
                l2 = l2.next;
            }
            // 移动cur指针到下一个节点。
            cur = cur.next;
        }

        // 如果l1还有剩余节点,直接接到cur后面。
        // 如果l2还有剩余节点,也直接接到cur后面。
        // 因为已经保证了l1和l2中的节点都是有序的,所以直接连接即可。
        cur.next = l1 == null ? l2 : l1;
        return dummpHead.next;
    }
}

删除有序链表中重复的元素-II

删除有序链表中重复的元素-II_牛客题霸_牛客网



import java.util.*;

/*
 * 定义一个单链表的节点类 ListNode。
 * 每个节点包含一个整数值 val 和一个指向下一个节点的引用 next。
 */
public class ListNode {
    int val;
    ListNode next = null; // 初始化时,下一个节点为空

    public ListNode(int val) {
        this.val = val; // 构造函数,用于创建一个新的节点并初始化其值
    }
}

public class Solution {
    /**
     * 删除排序链表中的重复元素。
     * 
     * @param head ListNode类 链表的头节点
     * @return ListNode类 删除重复元素后的链表头节点
     */
    public ListNode deleteDuplicates(ListNode head) {
        // 如果链表为空或只有一个节点,不需要删除重复,直接返回头节点
        if (head == null || head.next == null) {
            return head;
        }

        // 使用哑节点(dummy node)简化头节点处理
        ListNode dummyHead = new ListNode(-1);
        dummyHead.next = head;

        // cur 用于遍历链表
        ListNode cur = dummyHead;

        // 遍历链表,直到最后一个节点的前一个节点
        while (cur.next != null && cur.next.next != null) {
            // 如果当前节点和下一个节点的值相同,说明发现重复
            if (cur.next.val == cur.next.next.val) {
                int temp = cur.next.val; // 记录重复的值
                // 继续向后遍历,直到值不再相同
                while (cur.next != null && temp == cur.next.val) {
                    cur.next = cur.next.next; // 跳过重复的节点
                }
            } else {
                // 如果当前节点和下一个节点的值不相同,移动到下一个节点
                cur = cur.next;
            }
        }

        // 返回新链表的头节点
        return dummyHead.next;
    }
}

删除有序链表中重复的元素-I

删除有序链表中重复的元素-I_牛客题霸_牛客网

import java.util.*;

/*
 * 定义一个单链表的节点类 ListNode。
 * 每个节点包含一个整数值 val 和一个指向下一个节点的引用 next。
 */
public class ListNode {
    int val;
    ListNode next = null; // 初始化时,下一个节点为空

    public ListNode(int val) {
        this.val = val; // 构造函数,用于创建一个新的节点并初始化其值
    }
}

public class Solution {
    /**
     * 删除排序链表中的所有重复元素,保留第一次出现的元素。
     *
     * @param head ListNode类 链表的头节点
     * @return ListNode类 删除重复元素后的链表头节点
     */
    public ListNode deleteDuplicates(ListNode head) {
        // 如果链表为空,直接返回 null
        if (head == null) {
            return null;
        }

        // 使用哑节点(dummy node)简化头节点处理
        ListNode dummyHead = new ListNode(-1);
        dummyHead.next = head;

        // cur 用于遍历链表,从哑节点开始
        ListNode cur = dummyHead;

        // 遍历链表,直到最后一个节点
        while (cur.next != null) {
            // 如果当前节点和下一个节点的值相同,说明发现重复
            if (cur.next.val == cur.next.next != null && cur.next.val == cur.next.next.val) {
                int temp = cur.next.val; // 记录重复的值
                // 继续向后遍历,直到值不再相同
                while (cur.next != null && temp == cur.next.val) {
                    cur.next = cur.next.next; // 跳过重复的节点
                }
            } else {
                // 如果当前节点和下一个节点的值不相同,移动到下一个节点
                cur = cur.next;
            }
        }

        // 返回新链表的头节点
        return dummyHead.next;
    }
}

两数相加

我自己写的

// 定义链表节点类
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 addTwoNumbers(ListNode l1, ListNode l2) {
        int shouldAddOne = 0; // 进位标志,初始为0
        int returnList = 1; // 返回链表的标志,1表示l1,2表示l2

        ListNode l1First = l1; // 保存l1的头节点
        ListNode l2First = l2; // 保存l2的头节点

        ListNode l1End = l1; // 用于找到l1的尾节点
        ListNode l2End = l2; // 用于找到l2的尾节点

        // 遍历两个链表,直到都遍历完
        while (l1 != null || l2 != null) {
            if (l1 != null && l2 != null) { // 两个链表都有节点
                int temp = l2.val + l1.val + shouldAddOne; // 计算当前位的和
                shouldAddOne = temp >= 10 ? 1 : 0; // 更新进位
                l2.val = temp % 10; // 更新l2当前节点的值
                l1.val = temp % 10; // 更新l1当前节点的值
                returnList = 2; // 更新返回链表标志
                l1 = l1.next; // 移动到下一个节点
                l2 = l2.next; // 移动到下一个节点
            } else if (l1 == null && l2 != null) { // l1已遍历完,l2还有节点
                int temp = l2.val + shouldAddOne; // 计算当前位的和
                shouldAddOne = temp >= 10 ? 1 : 0; // 更新进位
                l2.val = temp % 10; // 更新l2当前节点的值
                returnList = 2; // 更新返回链表标志
                l2 = l2.next; // 移动到下一个节点
            } else { // l2已遍历完,l1还有节点
                int temp = l1.val + shouldAddOne; // 计算当前位的和
                shouldAddOne = temp >= 10 ? 1 : 0; // 更新进位
                l1.val = temp % 10; // 更新l1当前节点的值
                returnList = 1; // 更新返回链表标志
                l1 = l1.next; // 移动到下一个节点
            }
        }

        // 找到l1的尾节点
        while (l1End.next != null) {
            l1End = l1End.next;
        }
        // 找到l2的尾节点
        while (l2End.next != null) {
            l2End = l2End.next;
        }

        // 如果最后还有进位
        if (shouldAddOne == 1) {
            if (returnList == 1) { // 如果返回链表是l1
                l1End.next = new ListNode(1, null); // 在l1的尾部添加新节点
                return l1First; // 返回l1
            }
            if (returnList == 2) { // 如果返回链表是l2
                l2End.next = new ListNode(1, null); // 在l2的尾部添加新节点
                return l2First; // 返回l2
            }
        }

        // 返回最终的链表
        return returnList == 1 ? l1First : l2First;
    }
}

chatgpt写的

class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode dummyHead = new ListNode(0); // 创建一个虚拟头节点
        ListNode current = dummyHead; // 当前节点指针
        int carry = 0; // 进位

        while (l1 != null || l2 != null || carry != 0) {
            int sum = carry; // 进位加上当前位的和
            
            if (l1 != null) {
                sum += l1.val;
                l1 = l1.next; // 移动到下一个节点
            }
            
            if (l2 != null) {
                sum += l2.val;
                l2 = l2.next; // 移动到下一个节点
            }
            
            carry = sum / 10; // 更新进位
            current.next = new ListNode(sum % 10); // 创建新节点并连接
            current = current.next; // 移动到下一个节点
        }

        return dummyHead.next; // 返回结果链表,跳过虚拟头节点
    }
}

回文链表

/**
 * Definition for singly-linked list.
 * 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 boolean isPalindrome(ListNode head) {
        // 创建一个列表,用于存储链表中的节点值
        List<Integer> list = new ArrayList<>();
        // 创建一个指针,用于遍历链表
        ListNode temp = head;

        // 遍历链表,将每个节点的值添加到列表中
        while(temp != null){
            list.add(temp.val); // 将当前节点的值添加到列表
            temp = temp.next;   // 移动指针到下一个节点
        }

        // 初始化两个指针,分别指向列表的头部和尾部
        int left = 0;
        int right = list.size() - 1;

        // 使用双指针法检查列表是否为回文
        while(left < right){
            // 如果左右指针所指向的值不相等,则说明不是回文
            if(list.get(left) != list.get(right)){
                return false;
            }
            // 移动指针向中间靠拢
            left++;
            right--;
        }

        // 如果所有对应位置的值都相等,则说明是回文
        return true;
    }
}

面试题 02.04. 分割链表

/**
 * Definition for singly-linked list.
 * 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 partition(ListNode head, int x) {
        // 创建两个虚拟头节点,分别用于存储小于x的节点和大于等于x的节点
        ListNode beforeHead = new ListNode();
        ListNode afterHead = new ListNode();
        // 初始化两个指针,分别指向两个虚拟头节点
        ListNode before = beforeHead;
        ListNode after = afterHead;
        // 初始化当前节点指针,指向链表的头节点
        ListNode cur = head;
        
        // 遍历整个链表
        while(cur != null){
            // 如果当前节点的值小于x
            if(cur.val < x){
                // 将当前节点连接到before链表的末尾
                before.next = cur;
                // 移动before指针到新添加的节点
                before = before.next;
            } else {
                // 否则,将当前节点连接到after链表的末尾
                after.next = cur;
                // 移动after指针到新添加的节点
                after = after.next;
            }
            // 移动当前节点指针到下一个节点
            cur = cur.next;
        }
        // 将after链表的末尾置为null,防止形成环
        after.next = null;
        // 将before链表的末尾连接到after链表的头部(去掉虚拟头节点)
        before.next = afterHead.next;
        // 返回before链表的头部(去掉虚拟头节点)
        return beforeHead.next;
    }
}

哈希表

判断是否存在同一个元素

链表相交

LRU 缓存

class LRUCache {
    // 自定义双向链表节点类
    class MyNode{
        int key;   // 节点的键
        int value; // 节点的值
        MyNode pre;  // 前驱节点指针
        MyNode next; // 后继节点指针
        public MyNode(){}
        public MyNode(int key, int value){
            this.key = key;
            this.value = value;
        }
    }

    HashMap<Integer, MyNode> map; // 用于快速查找节点的哈希表
    int size;  // 当前缓存的大小
    int capacity; // 缓存的最大容量
    MyNode head, tail; // 双向链表的虚拟头节点和虚拟尾节点

    // 构造函数,初始化缓存
    public LRUCache(int capacity) {
        map = new HashMap<>();
        this.size = 0;
        this.capacity = capacity;
        head = new MyNode();  // 创建虚拟头节点
        tail = new MyNode();  // 创建虚拟尾节点
        head.next = tail; // 初始化头节点的后继为尾节点
        tail.pre = head;  // 初始化尾节点的前驱为头节点
    }
    
    // 获取缓存中的值
    public int get(int key) {
        if(!map.containsKey(key)){ // 如果缓存中不存在该键
            return -1;
        }
        moveToHead(key); // 将访问的节点移动到头部,表示最近访问
        return map.get(key).value; // 返回节点的值
    }
    
    // 向缓存中插入键值对
    public void put(int key, int value) {
        if(!map.containsKey(key)){ // 如果缓存中不存在该键
            MyNode newNode = new MyNode(key, value); // 创建新节点
            addToHead(newNode); // 将新节点添加到头部
            map.put(key, newNode); // 将新节点加入哈希表
            if(size > capacity){ // 如果当前缓存大小超过容量
                MyNode node = removeTail(); // 移除尾部节点(最久未访问)
                map.remove(node.key); // 从哈希表中移除该节点
            }
        }else{ // 如果缓存中已存在该键
            MyNode oldNode = map.get(key); // 获取旧节点
            oldNode.value = value; // 更新节点的值
            moveToHead(key); // 将节点移动到头部,表示最近访问
        }
    }

    // 将节点添加到头部
    void addToHead(MyNode node) {
        node.pre = head; // 设置节点的前驱为头节点
        node.next = head.next; // 设置节点的后继为头节点的后继
        head.next.pre = node;  // 修改头节点后继的前驱为当前节点
        head.next = node; // 修改头节点的后继为当前节点
        size++; // 缓存大小加1
    }

    // 将节点移动到头部
    void moveToHead(int key){
        MyNode node = map.get(key); // 从哈希表中获取节点
        removeNode(node); // 移除节点
        addToHead(node); // 将节点添加到头部
    }

    // 移除节点
    void removeNode(MyNode node){
        size--; // 缓存大小减1
        node.pre.next = node.next; // 修改前驱节点的后继为当前节点的后继
        node.next.pre = node.pre; // 修改后继节点的前驱为当前节点的前驱
    }

    // 移除尾部节点(最久未访问)
    MyNode removeTail(){
        MyNode node = tail.pre; // 获取尾部节点
        removeNode(node); // 移除尾部节点
        return node; // 返回移除的节点
    }
}

/**
 * Your LRUCache object will be instantiated and called as such:
 * LRUCache obj = new LRUCache(capacity);
 * int param_1 = obj.get(key);
 * obj.put(key,value);
 */

面试题 02.07. 链表相交 - 力扣(LeetCode)

public class Solution {
    // 定义一个方法来获取两个链表的交点节点
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        // 使用一个 HashSet 来存储链表 A 的所有节点
        Set<ListNode> hashset = new HashSet<ListNode>();

        // 创建一个指针 cur,用于遍历链表 A
        ListNode cur = headA;
        // 遍历链表 A,直到链表的末尾
        while(cur != null){
            // 将当前节点添加到 HashSet 中
            hashset.add(cur);
            // 移动到下一个节点
            cur = cur.next;
        }

        // 重新使用 cur 指针遍历链表 B
        cur = headB;
        // 遍历链表 B,直到链表的末尾
        while(cur != null){
            // 如果当前节点在 HashSet 中,说明找到了交点
            if(hashset.contains(cur)){
                // 返回交点节点
                return cur;
            }
            // 移动到下一个节点
            cur = cur.next;
        }
        // 如果没有交点,返回 null
        return null;
    }
}

环形链表

142. 环形链表 II - 力扣(LeetCode)

public class Solution {
    public ListNode detectCycle(ListNode head) {
        
        // 创建一个 HashSet 用来存储访问过的节点
        Set<ListNode> hashset = new HashSet<>();
        
        // 初始化当前节点为链表的头节点
        ListNode cur = head;
        
        // 循环遍历整个链表
        while (cur != null) {
            // 如果当前节点已经存在于 HashSet 中,说明链表有环,且该节点就是环的起点
            if (hashset.contains(cur)) {
                return cur;  // 返回环的起点节点
            } else {
                // 如果当前节点不在 HashSet 中,将其添加到集合中,表示已经访问过该节点
                hashset.add(cur);
            }
            // 移动当前指针到下一个节点
            cur = cur.next;
        }

        // 如果遍历完整个链表都没有找到重复的节点,说明链表无环,返回 null
        return null;
    }
}

面试题 02.01. 移除重复节点

/**
 * Definition for singly-linked list.
 * 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 removeDuplicateNodes(ListNode head) {
        // 使用HashSet来存储已经遇到的节点值,以便快速查找重复节点
        HashSet<Integer> set = new HashSet<>();
        // 创建一个虚拟头节点,简化边界条件处理
        ListNode dummpHead = new ListNode(-1);
        dummpHead.next = head;
        // 使用tmp指针遍历链表
        ListNode tmp = dummpHead;
        while(tmp.next != null){
            // 获取当前节点
            ListNode cur = tmp.next;
            // 如果当前节点的值不在集合中,说明是第一次遇到该值
            if(!set.contains(cur.val)){
                // 将该值加入集合
                set.add(cur.val);
                // 移动tmp指针到下一个节点
                tmp = tmp.next;
            }else{
                // 如果当前节点的值已经在集合中,说明是重复节点
                // 跳过当前节点,即删除当前节点
                tmp.next = tmp.next.next;
            }
        }
        // 返回处理后的链表头节点(虚拟头节点的下一个节点)
        return dummpHead.next;
    }
}

复制链表

复杂链表的复制

// 定义一个解决方案类,用于解决复制含有随机指针的链表问题。
class Solution {
    // 复制一个含有随机指针的链表
    public Node copyRandomList(Node head) {
        // 如果链表头为null,说明链表为空,直接返回null
        if(head == null){
            return null;
        }

        // 使用HashMap来存储原链表节点和复制节点的对应关系
        Map<Node, Node> map = new HashMap<>();
        // 定义一个指针cur,从头节点开始遍历链表
        Node cur = head;

        // 第一遍遍历:复制每个节点,并建立原节点和复制节点的对应关系
        while(cur != null){
            // 为当前节点创建一个值相同的复制节点,并放入map中
            map.put(cur, new Node(cur.val));
            // 移动到下一个节点
            cur = cur.next;
        }

        // 重置cur指针,从头节点开始第二遍遍历
        cur = head;

        // 第二遍遍历:设置复制节点的next和random指针
        while(cur != null){
            // 将当前节点的复制节点的next指针指向当前节点的下一个节点的复制节点
            map.get(cur).next = map.get(cur.next);
            // 将当前节点的复制节点的random指针指向当前节点的random节点的复制节点
            map.get(cur).random = map.get(cur.random);
            // 移动到下一个节点
            cur = cur.next;
        }

        // 返回复制链表的头节点
        return map.get(head);
    }
}

回溯

随机链表的复制

/*
// Definition for a Node.
class Node {
    int val; // 节点的值
    Node next; // 指向下一个节点的指针
    Node random; // 指向任意节点的随机指针

    public Node(int val) {
        this.val = val;
        this.next = null;
        this.random = null;
    }
}
*/

class Solution {
    Map<Node, Node> map = new HashMap<Node, Node>(); // 使用哈希表来存储原节点和新节点的映射关系

    public Node copyRandomList(Node head) {
        if(head == null){ // 如果头节点为空,则直接返回null
            return null;
        }

        if(!map.containsKey(head)){ // 如果哈希表中没有当前节点的映射关系
            Node newNode = new Node(head.val); // 创建一个新的节点,其值与原节点相同
            map.put(head, newNode); // 将原节点和新节点的映射关系存入哈希表
            newNode.next = copyRandomList(head.next); // 递归复制下一个节点,并将结果赋给新节点的next指针
            newNode.random = copyRandomList(head.random); // 递归复制随机指针指向的节点,并将结果赋给新节点的random指针
        }

        return map.get(head); // 返回哈希表中存储的当前原节点对应的新节点
    }
}

总结

以下针对你列出的各链表题目,逐一总结每题的核心思路和要点(按出现顺序):

61. 旋转链表 (Rotate List)

思路概览:先遍历一遍链表,计算长度 len,并记录尾节点 tail;计算实际需要移动的步数:k = k % len。如果 k == 0,直接返回原链表;从头节点走到第 len - k - 1 个节点,用指针 slow 定位切断点;将切断点后面的节点作为新头,原尾节点指向原头,断开切断点的 next;返回新的头节点。

关键点:必须先求链表长度,再对 k 取模,避免过度旋转;用哑节点或尾节点帮助调整指针,注意边界(空链表或 k%len == 0)。时间复杂度:O(n),空间复杂度:O(1)。

203. 移除链表元素 (Remove Linked List Elements)

思路概览:新建一个哑节点 dummyHead,其 next 指向 head,方便删除头节点;用两个指针 pre(前驱)和 cur(当前)来遍历:若 cur.val == val,则 pre.next = cur.next,跳过 cur;否则 pre = cur;始终 cur = cur.next,继续遍历;遍历结束后,返回 dummyHead.next。

关键点:使用“虚拟头节点”可统一处理删除头节点的情况;删除时只要改变 pre.next,无需额外释放节点(Java 会自动回收)。时间复杂度:O(n),空间复杂度:O(1)。

82. 删除排序链表中的重复元素 II (Remove Duplicates from Sorted List II)

思路概览:同样创建哑节点 dummyHead 指向 head,用指针 cur 从 dummyHead 开始;当 cur.next != null && cur.next.next != null 时,如果发现 cur.next.val == cur.next.next.val,说明出现重复:记住重复的值 dup = cur.next.val,然后不停地将 cur.next 指向 cur.next.next,跳过所有值为 dup 的节点;若没有重复,则 cur = cur.next,继续向前;返回 dummyHead.next。

关键点:一旦发现两连节点值相同,就要“跳过所有等于该值的节点”,而不仅仅是跳过两处;哑节点用来简化删除操作,尤其是头部节点为重复时;时间复杂度:O(n),空间复杂度:O(1)。

707. 设计链表 (Design Linked List)

思路概览:用单链表实现 get(index)、addAtHead(val)、addAtTail(val)、addAtIndex(index, val)、deleteAtIndex(index)。维护:size:当前链表长度;dummyHead:哑节点,其 next 指向实际头节点。各操作关键步骤:get(index):如果 index<0 或 index>=size 返回 -1;否则从 dummyHead.next 遍历 index 步,取出节点值;addAtHead(val):等价于 addAtIndex(0, val);addAtTail(val):从 dummyHead 遍历到末尾,插入新节点,并 size++;addAtIndex(index, val):如果 index > size,不插入;若 index < 0,视作在头部插入;从 dummyHead 遍历到第 index - 1 个位置,将新节点插入;size++;deleteAtIndex(index):若 index<0 或 index>=size,不删除;否则从 dummyHead 遍历到第 index - 1 个位置,cur.next = cur.next.next,size--。

关键点:哑节点:简化头节点插入/删除的边界处理;维护 size:保证 get、addAtIndex、deleteAtIndex 时能快速判断索引有效性;时间复杂度:各操作最坏 O(n)(遍历),空间复杂度:O(1)。

合并两个有序链表 (Merge Two Sorted Lists) —— LeetCode 21 (训练计划 IV 同理)

思路概览:新建哑节点 dummyHead,cur = dummyHead;用指针 l1 和 l2 分别遍历两个有序链表,当且仅当都非空时,比较 l1.val 与 l2.val:较小节点接到 cur.next,对应指针后移;cur = cur.next;循环结束后,将剩余节点(l1 或 l2 的非空部分)直接接到 cur.next;返回 dummyHead.next。

关键点:哑节点 帮助快速返回合并后链表头;循环中对两链表头进行“逐节点比较并拼接”,最后不必再遍历剩余部分;时间复杂度:O(n+m),空间复杂度:O(1)。

206. 反转链表 (Reverse Linked List)

思路概览(迭代):边界:若 head == null || head.next == null,直接返回 head;初始化两个指针:pre = null,cur = head;当 cur != null:nextTemp = cur.next(暂存下一个节点);cur.next = pre(反转指向);pre = cur,cur = nextTemp;循环结束后,pre 即为新头,返回 pre。

关键点:每次断开当前节点与后续的连接后,将其 next 指向前已反转部分;保留一个临时 nextTemp 避免链表断链;时间复杂度:O(n),空间复杂度:O(1)。

24. 两两交换链表中的节点 (Swap Nodes in Pairs)

思路概览:新建哑节点 dummyHead,dummyHead.next = head,prev = dummyHead;只要 prev.next != null && prev.next.next != null:令 first = prev.next,second = prev.next.next;交换步骤:prev.next = second;first.next = second.next;second.next = first;然后 prev = first(跳到已交换对的尾端),继续下一对;返回 dummyHead.next。

关键点:需要一个固定“前驱”prev 帮助定位和拼接;交换时先断开再重连,注意保存 second.next;时间复杂度:O(n),空间复杂度:O(1)。

19. 删除链表的倒数第 N 个结点 (Remove Nth Node From End)

思路概览:新建哑节点 dummyHead,dummyHead.next = head;设置 fast = dummyHead 和 slow = dummyHead;先让 fast 前进 n + 1 步,此时 fast 和 slow 相距 n 个节点;然后同时移动 fast、slow,直到 fast == null;此时 slow.next 即为要删除的「倒数第 n」节点:slow.next = slow.next.next;返回 dummyHead.next。

关键点:通过“快指针先走 n+1 步”,让“慢指针”最终停在待删节点的前驱处;使用哑节点简化当 n == 长度(删除头节点)的情况;时间复杂度:O(n),空间复杂度:O(1)。

删除有序链表中重复的元素-II(牛客题霸实现,与 LeetCode 82 类似)

思路概览:与 LeetCode 82 基本一致:用哑节点 dummy 指向 head,cur = dummy;当 cur.next != null && cur.next.next != null 时,如果发现 cur.next.val == cur.next.next.val:记录该重复值 val,然后不断执行 cur.next = cur.next.next,跳过所有相同节点;否则 cur = cur.next;返回 dummy.next。

关键点:对所有连续相同值节点一次性跳过;哑节点用于处理头部连续重复的情况;时间复杂度:O(n),空间复杂度:O(1)。

删除有序链表中重复的元素-I —— 保留一次出现 (Remove Duplicates from Sorted List I, LeetCode 83)

思路概览:若 head == null,直接返回 null。用指针 cur = head;遍历到链表末尾:当 cur.next != null 且 cur.val == cur.next.val 时:执行 cur.next = cur.next.next,跳过下一个重复节点;否则 cur = cur.next;返回 head。

关键点:只需删除“连续重复”的后一节点,保留第一次出现;无需哑节点,因为不处理头以外的特殊删除;时间复杂度:O(n),空间复杂度:O(1)。

两数相加 (Add Two Numbers, LeetCode 2)

思路概览:新建哑节点 dummyHead,cur = dummyHead,carry = 0;当 l1 != null || l2 != null || carry != 0 时:设 sum = carry;若 l1 != null,则 sum += l1.val、l1 = l1.next;若 l2 != null,则 sum += l2.val、l2 = l2.next;carry = sum / 10;cur.next = new ListNode(sum % 10),cur = cur.next;最后返回 dummyHead.next。

关键点:同时遍历两条链,用 carry 处理进位;循环条件要包含 “carry != 0”,以防最高位还需新加一位;哑节点简化链表拼接。时间复杂度:O(max(m, n)),空间复杂度:O(max(m, n))(返回新链表)。

回文链表 (Palindrome Linked List, LeetCode 234)

思路概览:方法一(借助列表):遍历链表,将所有节点值依次存入 List<Integer> vals;用双指针 i=0, j=vals.size()-1,逐对比 vals.get(i) 与 vals.get(j);若都相等,则继续;否则返回 false。如果全部对比完成,返回 true。方法二(快慢指针 + 反转后半链表):用快慢指针找到链表中点;将后半部分链表原地反转;比较前半链表与反转后半链表对应节点值;最后(可选)恢复链表结构。

关键点:借助 ArrayList 实现最简单,但空间 O(n);优化方案:快慢指针 + 原地反转后半段 --> 空间 O(1);时间复杂度:O(n),空间复杂度:方法一 O(n),方法二 O(1)。

LRU 缓存 (LRU Cache, LeetCode 146)

思路概览:维护一个 双向链表 + 哈希表:双向链表 用于记录访问顺序,链表头是“最近使用”,链表尾是“最久未使用”;哈希表 用于 key -> 节点 的快速访问;操作:get(key):若 key 不存在于哈希表,返回 -1;否则通过哈希表拿到对应节点,将其移动到链表头(表示最近访问),并返回节点的 value;put(key, value):若 key 不存在:创建新节点,插入到链表头,写入哈希表;若此时 size > capacity,移除链表尾节点,并从哈希表中删除该 key;若 key 已存在:更新该节点的 value,并将该节点移动到链表头;链表细节:设计“虚拟头节点”和“虚拟尾节点”,简化插入/删除操作;addToHead(node):把 node 插到 head 之后;removeNode(node):从链表中拔掉 node;moveToHead(node):先 removeNode(node),再 addToHead(node);removeTail():移除链表尾部(tail.pre)并返回该节点。

关键点:哈希表实现 O(1) 的节点定位;双向链表实现 O(1) 的插入/删除;必须同时维护这两种结构才能保证操作均摊 O(1);时间复杂度:O(1) 均摊读写,空间复杂度:O(capacity)。

面试题 02.07. 链表相交 (Intersection of Two Linked Lists, LeetCode 160)

思路概览:哈希法(如示例):遍历链表 A,将所有节点引用(ListNode 对象)加入 HashSet;遍历链表 B,如果某节点已在集合中,说明为交点,直接返回该节点;否则继续;若遍历完 B 仍无交点,返回 null。双指针法(更常见)(不在题述中,但常用):令 pa = headA,pb = headB。同时向前遍历;若 pa == null,则 pa = headB;若 pb == null,则 pb = headA;这样两指针走过的总长度相同,最终要么在交点相遇,要么都为 null。

关键点:哈希法空间复杂度 O(m);双指针法空间 O(1);哈希法实现简单直观,但若输入规模较大且空间敏感,可考虑双指针。时间复杂度:O(m+n),哈希法空间 O(m);双指针法空间 O(1)。

142. 环形链表 II (Linked List Cycle II)

思路概览:哈希法(如示例):遍历时将访问过的节点存入 HashSet;当某个节点即将访问时发现已在集合中,则此节点为链表环的入口,返回它;若遍历完链表无重复,则返回 null。快慢指针法(Floyd 判圈)(常见,未在示例中):先用快慢指针判断是否有环:slow = slow.next,fast = fast.next.next;若相遇,说明有环;否则无环返回 null;如果相遇,在相遇点分别令 p1 = head、p2 = meetingPoint,两指针同步向前(每次都 = .next),再次相遇即为环入口。

关键点:哈希法简单,但空间 O(n);快慢指针法空间 O(1);快慢指针判定有环后,从头节点和相遇点重新同步走,最终交汇即入环点。时间复杂度:O(n),哈希法空间 O(n),快慢指针空间 O(1)。

复制带随机指针的链表 (Copy List with Random Pointer, LeetCode 138)

思路概览:哈希映射法(如示例):第一遍遍历:cur 从 head 开始,遇到每个原节点 node,在 map 中创建一个新节点 newNode = new Node(node.val),并使 map.put(node, newNode);第二遍遍历:重置 cur = head,对于每个原节点 node:map.get(node).next = map.get(node.next);map.get(node).random = map.get(node.random);最后返回 map.get(head)。三步法(无额外映射):复制每个节点并插入原节点后面:原链表 A→B→C 变成 A→A'→B→B'→C→C';设置随机指针:对于每个原节点 node,其复制节点 node.next.random = node.random.next(若 node.random != null);拆分两个链表:将原链表和复制链表拆开,还原各自的 next 指针;最终返回复制链表的头节点。

关键点:哈希映射法简单易懂但空间 O(n);三步法空间 O(1),但链表指针操作需谨慎(先插入,再赋 random,再拆分)。时间复杂度:O(n),哈希法空间 O(n),三步法空间 O(1)。

两数相加(你自己与 ChatGPT 版本对比)

思路概览与要点:核心思路一致:维护 carry 进位;同时遍历 l1 和 l2,相加并产生新节点;对于较短链表,当另一条链表还有节点时,也要加上 carry;最后若 carry == 1,需要额外再加一个新节点。区别点:你自己的实现思路是对原链表就地修改,并通过 returnList 标记哪条链表较长;ChatGPT 的版本通过新建哑节点构造全新返回链表,更加直观、不修改输入;关键点:不要忘记最后处理 carry;哑节点能够统一收尾逻辑;时间复杂度:O(max(m, n)),空间复杂度:如果就地修改,空间 O(1);如果新建,空间 O(max(m, n))。

回文链表(再次总结)已在第 12 条中给出两种常见做法,概念重复,此处不再赘述。

哈希表部分:判断是否存在同一个元素(示例中并未详细展开)主要思路:将遍历到的节点引用(ListNode 对象)存入 HashSet,如果后续再次遇到相同引用,就说明存在。这在检测交点或环时都可借鉴。

链表相交(已在第 14 条概述)

LRU 缓存(已在第 13 条概述)

面试题 02.07. 链表相交(已在第 14 条概述)

142. 环形链表 II(已在第 15 条概述)

复制链表 & 复杂链表复制(已在第 16 条概述)

总结性要点归纳

哑节点(Dummy/虚拟头节点):几乎所有涉及“头节点可能被删除或插入”的题目,都可以通过在最前面添加一个哑节点,来简化边界情况处理。

双指针技巧:“快慢指针”常用于寻找链表中间、判断有无环并定位环入口。“前驱+当前”指针组合常用于删除操作。“同时遍历两链表,或先走 n 步再遍历”是删除倒数第 n、合并有序链表、相交链表等题目的高频思路。

链表原地修改 vs. 新建结果链表:有些算法可以就地修改原链表(例如 “局部删除”),也有一些需要返回一个全新链表(例如 “两数相加”、“复杂链表复制”)。若不想破坏原结构,就需要借助额外节点或哈希映射;若可就地修改,则要格外留意指针断开与重连。

哈希表辅助:用于检测“是否出现过某个节点引用”(判断相交、检测环、复制带随机指针)。空间换时间,常以 O(n) 空间来实现相同的时间复杂度优化。

原地反转链表:反转链表问题只需要两个指针(prev、cur)不断变更 cur.next,并保留 nextTemp = cur.next,逐步推进。在“回文链表”中,也会用到“反转后半链表”的技巧。

链表设计:需要维护长度(size)、哑节点,并用“遍历到第 index - 1 个节点”来插入或删除。所有索引判断都要基于 size 做边界检查。

特殊链表题目思路汇总:旋转链表:先求长、取模、找到新尾、接尾后断、重连。移除元素:双指针或哑节点,一次遍历 O(n)。删除排序重复(I/II):I 只删多余,II 要删全重复段;都离不开“比较 cur.next 与 cur.next.next”。合并有序:指针依次拼接,最后直连剩余。反转:迭代反转每个节点指向。成对交换:用哑节点 + 每次取 first/second,三步拼接。删除倒数第 n:快慢先走定距离,再同速共行。两数相加:并行遍历两链 + carry 进位,哑节点新链。回文链表:可借助数组/栈,或快慢+后半反转。LRU:哈希表 + 双向链表维护访问顺序。链表相交:哈希法或双指针 pa→headB, pb→headA。环形链表 II:哈希法或 Floyd 快慢指针找入口。复制带随机指针:哈希映射两遍填 next/random,或三步法原地插入+拆分。

以上即为各题的核心思路与常见要点,供复习时快速回顾。希望对你理解和攻克链表题有所帮助!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值