【力扣hot100】链表专题|160、206、234、141、142

链表专题

链表是数据结构中的"常青树",也是面试中绕不开的经典考点。本专题将带你从基础操作入手,逐步攻克反转、回文判断、环检测等高频难题,重点剖析快慢指针等核心技巧,助你彻底掌握链表的精髓,为后续学习更复杂的数据结构打下坚实基础。

文章目录

  • 链表专题
      • [160. 相交链表](#160. 相交链表)
      • [206. 反转链表](#206. 反转链表)
      • [234. 回文链表](#234. 回文链表)
      • 进阶
      • [141. 环形链表](#141. 环形链表)
      • 优化
      • [142. 环形链表 II](#142. 环形链表 II)
      • 优化

160. 相交链表

160. 相交链表

java 复制代码
/**
 * 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) {
        Set<ListNode>visited = new HashSet<ListNode>();
        ListNode temp = headA;
        while (temp != null) {
            visited.add(temp);
            temp = temp.next;
        }
        temp = headB;
        while (temp != null) {
            if (visited.contains(temp)) {
                return temp;
            }
            temp = temp.next;
        }
        return null;
    }
}

遍历一个链表存入Set里,再遍历另一个查找有没有一样的

206. 反转链表

206. 反转链表

反转链表需要三个变量:

  • ListNode prev = null; // 1. 记录"前一个节点"

  • ListNode curr = head; // 2. 记录"当前节点"

  • ListNode nextTemp = null; // 3. 记录"下一个节点"

    nextTemp = curr.next; // 第一步:先保存后继节点
    curr.next = prev; // 第二步:安全地指针
    prev = curr; // 第三步:prev 往前走一步
    curr = nextTemp; // 第四步:curr 往前走一步

java 复制代码
/**
 * 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 reverseList(ListNode head) {
        ListNode prev = null;
        ListNode curr = head;
        while (curr != null) {
            ListNode next = curr.next;
            curr.next = prev;
            prev = curr;
            curr = next;
        }
        return prev;
    }
}

234. 回文链表

234. 回文链表

java 复制代码
/**
 * 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> vals = new ArrayList<Integer>();

        // 将链表的值复制到数组中
        ListNode currentNode = head;
        while (currentNode != null) {
            vals.add(currentNode.val);
            currentNode = currentNode.next;
        }

        // 使用双指针判断是否回文
        int front = 0;
        int back = vals.size() - 1;
        while (front < back) {
            if (!vals.get(front).equals(vals.get(back))) {
                return false;
            }
            front++;
            back--;
        }
        return true;
    }
}

List 不能使用 [] 来获取或设置元素

== vs equals()

比较方式 含义
== 比较两个引用是否指向同一个对象(即内存地址是否相同)
.equals() 比较两个对象的内容是否相等(可被重写)

进阶

把空间复杂度降为O(1)

使用两个快慢指针找到前一半的结尾(找到链表的中点分割两部分):

  • 快指针走2步
  • 慢指针走1步

具体过程

  1. 寻找链表的中心元素把链表一分为二
  2. 翻转后半段链表
  3. 对比两部分链表
java 复制代码
/**
 * 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) {
        //寻找链表的中心元素把链表一分为二
        ListNode slow = head;
        ListNode fast = head.next;
        while(fast != null && fast.next != null){
            slow = slow.next;
            fast = fast.next.next;
        }
        //翻转后半段链表 并把前半段和后半段分开
        ListNode second = reverseList(slow.next);
        slow.next = null;
        //对比两部分链表
        ListNode first = head;
        while(first != null && second != null){
            if(first.val != second.val)return false;
            first = first.next;
            second  =second.next;
        }
        return true;
    }

    private ListNode reverseList(ListNode head){
        if(head == null)return head;

        ListNode curr = head;
        ListNode pre = null;
        while(curr != null){
            ListNode tempNext = curr.next;
            curr.next = pre;
            pre = curr;
            curr = tempNext;
        }
        return pre;
    }
}

为啥快指针走2 慢指针走1?

因为快指针速度是慢指针的2倍,所以走过的路程也是慢指针的2倍,当快指针到达终点时,慢指针刚好在总长的一半
如果链表时是奇数,slow正好指到链表中点,分割出来的前半部分多一个节点,对判断有影响吗?

没有影响,因为while(first != null && second != null)所以多出来的那个不比较,second空了就结束了

141. 环形链表

141. 环形链表

java 复制代码
public class Solution {
    public boolean hasCycle(ListNode head) {
        Set<ListNode> seen = new HashSet<ListNode>();
        while (head != null) {
            if (!seen.add(head)) {
                return true;
            }
            head = head.next;
        }
        return false;
    }
}
  • seen.add(head):尝试把当前节点head添加到集合中。
    • 如果该节点之前没访问过 ,添加成功,返回 true
    • 如果该节点已经存在 (说明之前访问过),添加失败,返回 false

优化

设置两个快慢指针,快指针套圈追赶慢指针,套到了就证明链表有回路

java 复制代码
public class Solution {
    public boolean hasCycle(ListNode head) {
        if (head == null) return false;
        ListNode fast = head;
        ListNode slow = head;

        while(fast.next != null && fast.next.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            if(fast == slow) return true;
        }

        return false;
    }
}

142. 环形链表 II

142. 环形链表 II

在添加进Set前加一步,如果Set包含该节点就说明该节点是入环的第一个节点

java 复制代码
/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode pos = head;
        Set<ListNode> visited = new HashSet<ListNode>();
        while (pos != null) {
            if (visited.contains(pos)) {
                return pos;
            } else {
                visited.add(pos);
            }
            pos = pos.next;
        }
        return null;
    }
}

优化

+c 只是 k=1k=1 时的特例。无论快指针套了多少圈,只要两个指针重新以相同的速度(1步/次)前进,它们一定会在环的入口相遇

java 复制代码
/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
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){
                //把快指针移回起点,速度一样,两指针相遇的地方就是环入口
                fast = head;
                while(fast != slow){
                    fast = fast.next;
                    slow = slow.next;
                }
                return fast;
            }
        }
        return null;
    }
}

为啥要判断 fast != null && fast.next != null

为了保证fast.next.next 这行代码能安全地执行

相关推荐
数模竞赛Paid answer2 小时前
2026年华东杯数学建模B题医药物流安排问题解题全过程文档及程序
算法·数学建模·数据分析·华东杯
不会代码的小猴3 小时前
21. 泛型编程上
开发语言·c++·笔记·算法
营养充电站3 小时前
VS Code Git 工作树:解锁多分支并行开发的高效体验
leetcode·决策树·逻辑回归·散列表·模拟退火算法
IKUN家族3 小时前
Spring IoC&DI
java·spring·rpc
AI备案指南-满满4 小时前
大模型与算法备案全流程详解:从零到通过的完整指南
人工智能·算法·备案·大模型备案·算法备案
江畔柳前堤4 小时前
LLM 训练核心机制深度解析:Warmup、Cosine Decay 与 Perplexity 的完整知识体系
网络·人工智能·深度学习·算法·机器学习·语音识别
一只旭宝4 小时前
细讲C加加【9】C++ std::function与std::bind详解|仿函数、绑定器、类成员绑定、占位符、成员偏移指针
开发语言·c++·算法
良木林4 小时前
子串 - LeetCode hot 100
算法·leetcode·职场和发展