剑指Offer算法题(四)链表

目录

[6. 从尾到头打印链表](#6. 从尾到头打印链表)

[18.1 在 O(1) 时间内删除链表节点](#18.1 在 O(1) 时间内删除链表节点)

[18.2 删除链表中重复的结点](#18.2 删除链表中重复的结点)

[22. 链表中倒数第 K 个结点](#22. 链表中倒数第 K 个结点)

[23. 链表中环的入口结点](#23. 链表中环的入口结点)

[24. 反转链表](#24. 反转链表)

[25. 合并两个排序的链表](#25. 合并两个排序的链表)

[35. 复杂链表的复制](#35. 复杂链表的复制)

[52. 两个链表的第一个公共结点](#52. 两个链表的第一个公共结点)


6. 从尾到头打印链表

java 复制代码
package linkedList;

import java.util.ArrayList;
import java.util.Stack;

// ====================== 核心思路 ======================
// 题目:从尾到头打印链表,用数组返回结果
// 解法:利用栈 "先进后出" 的特性实现逆序
// 1. 遍历链表,把所有节点值依次压入栈
// 2. 依次弹出栈中元素,存入 ArrayList
// 3. 最终得到的集合就是链表从尾到头的顺序

// 时间复杂度:O(n) 遍历两次链表
// 空间复杂度:O(n) 需要一个栈存储所有节点
// ======================================================


public class PrintLinkedListFromTailToHead {
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        Stack<Integer> stack = new Stack<>();
        while (listNode != null) {
            stack.add(listNode.val);
            listNode = listNode.next;
        }
        ArrayList<Integer> res = new ArrayList<>();
        while (!stack.isEmpty()) {
            res.add(stack.pop());
        }
        return res;
    }

    public static void main(String[] args) {
        ListNode listNode = new ListNode(1);
        listNode.next = new ListNode(2);
        listNode.next.next = new ListNode(3);
        PrintLinkedListFromTailToHead test = new PrintLinkedListFromTailToHead();
        System.out.println(test.printListFromTailToHead(listNode));
    }

}

18.1 在 O(1) 时间内删除链表节点

java 复制代码
package linkedList;

// ====================== 核心思路 ======================
// 题目:O(1) 时间复杂度删除链表指定节点
// 解法:不遍历找前驱,直接复制后继节点覆盖当前节点
// 1. 若待删节点不是尾节点:用下一个节点的值和指针覆盖它,O(1) 完成删除
// 2. 若待删节点是尾节点:只能从头遍历找到前驱,断开连接
// 3. 若链表只有一个节点(头节点=待删节点):直接返回 null

// 时间复杂度:平均 O(1),最坏 O(n)(删除尾节点)
// 空间复杂度:O(1)
// ======================================================

public class DeleteNodeInLinkedList {
    public ListNode deleteNode(ListNode head, ListNode tobeDelete) {
        // write code here
        if (head == null || tobeDelete == null) {
            return null;
        }
        if (tobeDelete.next == null) {
            if (head == tobeDelete) {
                return null;
            } else {
                ListNode cur = head;
                while (cur.next != tobeDelete) cur = cur.next;
                cur.next = null;
            }
        } else {
            ListNode temp = tobeDelete.next;
            tobeDelete.val = temp.val;
            tobeDelete.next = temp.next;
        }
        return head;
    }
}

18.2 删除链表中重复的结点

java 复制代码
package linkedList;

// ====================== 核心思路 ======================
// 题目:删除排序链表中所有重复的节点(重复节点不保留)
// 解法:递归法,利用排序链表重复节点相邻的特性
// 1. 递归终止条件:节点为空 或 只有一个节点,直接返回
// 2. 如果当前节点与下一个节点值相等:跳过所有重复节点,递归处理后续节点
// 3. 如果不相等:保留当前节点,递归处理下一个节点并连接
// 4. 最终返回去重后的链表头节点

// 时间复杂度:O(n) 遍历一次链表
// 空间复杂度:O(n) 递归调用栈空间
// ======================================================

public class RemoveDuplicateNodesInSortedList {
    public ListNode deleteDuplication(ListNode pHead) {
        if (pHead == null || pHead.next == null) {
            return pHead;
        }
        ListNode next = pHead.next;
        if (pHead.val == next.val) {
            while (next != null && pHead.val == next.val) {
                next = next.next;
            }
            return deleteDuplication(next);
        } else {
            pHead.next = deleteDuplication(pHead.next);
            return pHead;
        }
    }

    public static void main(String[] args) {
        ListNode listNode = new ListNode(1);
        listNode.next = new ListNode(2);
        listNode.next.next = new ListNode(3);
        listNode.next.next.next = new ListNode(3);
        listNode.next.next.next.next = new ListNode(4);
        listNode.next.next.next.next.next = new ListNode(4);
        listNode.next.next.next.next.next.next = new ListNode(5);
        RemoveDuplicateNodesInSortedList test = new RemoveDuplicateNodesInSortedList();
        ListNode res = test.deleteDuplication(listNode);
        while (res != null) {
            System.out.println(res.val);
            res = res.next;
        }
    }
}

22. 链表中倒数第 K 个结点

java 复制代码
package linkedList;

// ====================== 核心思路 ======================
// 题目:输出链表的倒数第k个节点
// 解法:快慢指针(双指针),一次遍历搞定,空间 O(1)
// 1. 快指针先走 k-1 步,指向第k个节点
// 2. 若快指针无法走完k-1步,说明链表长度不足k,返回null
// 3. 然后快慢指针同时向后走,直到快指针到达链表尾部
// 4. 此时慢指针指向的就是倒数第k个节点

// 时间复杂度:O(n) 一次遍历
// 空间复杂度:O(1) 仅用两个指针
// ======================================================

public class FindKthToTailInLinkedList {
    public ListNode FindKthToTail(ListNode pHead, int k) {
        // write code here
        if (pHead == null || k == 0) return null;
        ListNode pTail = pHead;
        while (pTail.next != null && k > 1) {
            pTail = pTail.next;
            k--;
        }
        if (k > 1) return null;
        while (pTail.next != null) {
            pHead = pHead.next;
            pTail = pTail.next;
        }
        return pHead;
    }
}

23. 链表中环的入口结点

java 复制代码
package linkedList;

// ====================== 核心思路 ======================
// 题目:找到链表中环的入口节点,无环则返回null
// 解法:快慢指针法(龟兔赛跑)
// 1. 快指针走2步,慢指针走1步,相遇则说明有环
// 2. 快指针回到链表头部,快慢指针都走1步
// 3. 再次相遇的位置,就是环的入口

// 时间复杂度 O(n):
// 快慢指针最多遍历整个链表两次(找相遇点+找入口点),常数次遍历仍为线性复杂度
// 空间复杂度 O(1):
// 仅使用 fast、slow 两个指针,无额外辅助空间,是原地算法
// ======================================================

public class EntryNodeOfLinkedListLoop {
    public ListNode EntryNodeOfLoop(ListNode pHead) {
        //快慢指针法(龟兔赛跑法)使用双指针,一个快指针 fast 每次移动两个节点,一个慢指针 slow 每次移动一个节点。因为存在环,所以两个指针必定相遇在环中的某个节点上。
        if (pHead == null) return null;
        ListNode pFast = pHead, pSlow = pHead;

        // 第一步:判断是否有环
        while (pFast != null && pFast.next != null) {
            pFast = pFast.next.next;
            pSlow = pSlow.next;
            if (pFast == pSlow) {
                break;
            }
        }

        // 无环直接返回
        if(pFast == null || pFast.next == null){
            return null;
        }

        // 第二步:找环入口
        pFast = pHead;
        while (pFast != pSlow) {
            pFast = pFast.next;
            pSlow = pSlow.next;
        }
        return pFast;
    }
}

24. 反转链表

java 复制代码
package linkedList;

import java.util.Stack;

// ====================== 核心思路 ======================
// 题目:反转单链表,返回反转后的新头节点
// 解法:迭代法(三指针反转),原地修改链表指向,不占用额外空间
// 1. 定义三个指针 pre、cur、nxt 分别表示前驱、当前、后继节点
// 2. 遍历链表,逐个将当前节点的 next 指向前驱节点
// 3. 不断更新指针位置,直到遍历结束
// 4. 最终 pre 指向新的头节点

// 时间复杂度:O(n) 遍历一次链表
// 空间复杂度:O(1) 只用了常数个指针变量
// ======================================================

public class ReverseList {
    public ListNode ReverseList(ListNode head) {
        // write code here
        if (head == null || head.next == null) return head;
        ListNode cur = head;
        ListNode pre = null;
        ListNode nxt;
        while (cur != null) {
            nxt = cur.next;
            cur.next = pre;
            pre = cur;
            cur = nxt;
        }
        return pre;
    }

    public static void main(String[] args) {
        ReverseList test = new ReverseList();
        ListNode head = new ListNode(1);
        head.next = new ListNode(2);
        head.next.next = new ListNode(3);
        ListNode listNode = test.ReverseList(head);
        while (listNode != null) {
            System.out.println(listNode.val);
            listNode = listNode.next;
        }
    }
}

25. 合并两个排序的链表

java 复制代码
package linkedList;

public class MergeTwoSortedLists {

    // ====================== 核心思路 ======================
    // 题目:合并两个递增排序的链表,合并后依然有序
    // 解法:双指针迭代法,原地合并,不占用额外空间
    // 1. 创建虚拟头节点,方便统一处理链表头部
    // 2. 用双指针同时遍历两个有序链表
    // 3. 每次取较小值的节点接入结果链表
    // 4. 一个链表遍历完后,直接拼接另一个链表剩余部分

    // 时间复杂度:O(n) 遍历一次两个链表
    // 空间复杂度:O(1) 仅使用常数个指针变量
    // ======================================================

    public ListNode Merge(ListNode pHead1, ListNode pHead2) {
        // write code here
        ListNode res = new ListNode(-1);
        ListNode cur = res;
        while (pHead1 != null && pHead2 != null) {
            if (pHead1.val < pHead2.val) {
                cur.next = pHead1;
                pHead1 = pHead1.next;
            } else {
                cur.next = pHead2;
                pHead2 = pHead2.next;
            }
            cur = cur.next;
        }
        if (pHead1 != null) {
            cur.next = pHead1;
        } else {
            cur.next = pHead2;
        }
        return res.next;
    }

    // ====================== 核心思路 ======================
    // 题目:合并两个递增排序的链表,合并后依然有序
    // 解法:递归法(简洁优雅)
    // 1. 递归终止条件:一个链表为空,直接返回另一个链表
    // 2. 比较两个链表头节点值大小
    // 3. 值小的节点作为当前头,它的 next 指向剩余部分递归合并的结果
    // 4. 层层返回,自动拼接成完整有序链表

    // 时间复杂度:O(n) 每个节点递归处理一次
    // 空间复杂度:O(n) 递归调用栈占用空间
    // ======================================================

    public ListNode Merge1(ListNode pHead1, ListNode pHead2) {
        if (pHead1 == null) return pHead2;
        if (pHead2 == null) return pHead1;
        if (pHead1.val < pHead2.val) {
            pHead1.next = Merge1(pHead1.next, pHead2);
            return pHead1;
        } else {
            pHead2.next = Merge1(pHead1, pHead2.next);
            return pHead2;
        }
    }

    public static void main(String[] args) {
        MergeTwoSortedLists test = new MergeTwoSortedLists();
        ListNode pHead1 = new ListNode(-1);
        pHead1.next = new ListNode(2);
        pHead1.next.next = new ListNode(4);
        ListNode pHead2 = new ListNode(1);
        pHead2.next = new ListNode(3);
        pHead2.next.next = new ListNode(4);
        ListNode listNode = test.Merge1(pHead1, pHead2);
        while (listNode != null) {
            System.out.println(listNode.val);
            listNode = listNode.next;
        }
    }
}

35. 复杂链表的复制

java 复制代码
package linkedList;

// ====================== 核心思路 ======================
// 题目:复制复杂链表(包含 next 和 random 指针)
// 解法:原地三步法(空间 O(1))
// 1. 在每个原节点后面插入一个克隆节点
// 2. 根据原节点的 random 给克隆节点赋值 random
// 3. 拆分原链表与克隆链表,得到深拷贝结果

// 时间复杂度:O(n)
// 空间复杂度:O(1)
// ======================================================

public class CopyRandomList {
    public RandomListNode Clone(RandomListNode pHead) {
        if (pHead == null) return null;
        RandomListNode cur = pHead;

        // 每个节点后面接一个复制的节点
        while (cur != null) {
            RandomListNode curCopy = new RandomListNode(cur.label);
            curCopy.next = cur.next;
            cur.next = curCopy;
            cur = curCopy.next;
        }
        // 给复制的节点赋random指针
        cur = pHead;
        while (cur != null) {
            RandomListNode curCopy = cur.next;
            if (cur.random != null) curCopy.random = cur.random.next;
            cur = curCopy.next;
        }

        // 剪出curCopy
        RandomListNode res = pHead.next;
        cur = pHead;
        while (cur.next != null) {
            RandomListNode next = cur.next;
            cur.next = next.next;
            cur = next;
        }
        return res;
    }
}

52. 两个链表的第一个公共结点

java 复制代码
package linkedList;

// ====================== 核心思路 ======================
// 题目:寻找两个无环单向链表的第一个公共节点
// 解法:双指针浪漫相遇法(最优解)
// 1. 定义两个指针 cur1、cur2 分别从两个链表头出发
// 2. 一个指针走到末尾后,指向另一个链表的头部继续走
// 3. 最终两个指针会在第一个公共节点相遇(或同时为null)
// 原理:路程相等,速度相同,必然相遇

// 时间复杂度:O(n) 两个指针各遍历两次链表
// 空间复杂度:O(1) 仅使用两个指针,无额外空间
// ======================================================

public class FindFirstCommonNode {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        ListNode cur1 = pHead1, cur2 = pHead2;
        while (cur1 != cur2) {
            cur1 = (cur1 == null) ? pHead2 : cur1.next;
            cur2 = (cur2 == null) ? pHead1 : cur2.next;
            // if (cur1 != null) {
            //     cur1 = cur1.next;
            // } else { // 走到null切换走另一个
            //     cur1 = pHead2;
            // }
            // if (cur2 != null) {
            //     cur2 = cur2.next;
            // } else {
            //     cur2 = pHead1;
            // }
        }
        return cur1;
    }
}
相关推荐
myloveasuka1 小时前
[Java]查找算法&排序算法
java·算法·排序算法
清水白石0082 小时前
Free-Threaded Python 实战指南:机遇、风险与 PoC 验证方案
java·python·算法
We་ct2 小时前
LeetCode 148. 排序链表:归并排序详解
前端·数据结构·算法·leetcode·链表·typescript·排序算法
本喵是FW2 小时前
C语言手记1
java·c语言·算法
咱就是说不配啊3 小时前
3.19打卡day33
数据结构·c++·算法
2501_924952693 小时前
嵌入式C++电源管理
开发语言·c++·算法
2401_842623653 小时前
C++中的访问者模式高级应用
开发语言·c++·算法
森林里的程序猿猿3 小时前
垃圾收集器G1和ZGC
java·jvm·算法
机器学习之心3 小时前
LSBoost增强算法回归预测+SHAP可解释分析+新数据预测(多输入单输出)MATLAB代码
算法·matlab·回归·lsboost·shap可解释分析