【力扣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 这行代码能安全地执行

相关推荐
鲨鱼辣钊1 小时前
【FastAPI筑基-Day19】APScheduler定时任务全实战|自动执行、动态启停、后台常驻
java·spring·fastapi
不正经学生3 小时前
C语言预处理详解:编译器真正动手之前的那些事
c语言·开发语言·算法·面试·bug
毕竟是shy哥6 小时前
计算YOLO数据集中每个类的目标数
算法·yolo·机器学习
M78佐菲6 小时前
Linux学习笔记:TCP协议
linux·笔记·学习·tcp/ip·算法
学长毕业设计6 小时前
基于SpringBoot的公益基金管理系统(源码+文档+讲解视频)
java·spring boot·后端
东小西6 小时前
【SAA实战】第 3 篇 · 工具调用全攻略:把业务能力交给 Agent 自己调度
java·后端·spring
东小西6 小时前
【SAA实战】第 4 篇 · Agent 短期记忆:saver 让 Agent 跨轮记得住(threadId 隔离)
java·后端·spring
圣保罗的大教堂6 小时前
leetcode 1406. 石子游戏 III 困难
leetcode
许彰午6 小时前
22-DataCenter报文序列化
java·低代码·架构·状态模式
2601_962065256 小时前
[MySQL] SQL优化之性能分析
java·sql·mysql