链表通关八题:从反转链表到两数相加,手撕LeetCode高频题

链表是面试和笔试的基础常客。本文整理了8道经典链表题目,每道题都包含:题目描述、解法思路、图解、Python代码、C++代码。全部理解后,链表类题目基本通关。

📌 目录

  1. 反转链表(迭代 + 递归)
  2. 相交链表
  3. 移除链表元素
  4. 删除链表的倒数第 N 个结点
  5. 两两交换链表中的节点
  6. 环形链表(判环)
  7. 环形链表 II(找入环点)
  8. 两数相加

反转链表

题目描述

给你单链表的头节点 head,请你反转链表,并返回反转后的链表。

示例:

输入:1 -> 2 -> 3 -> 4 -> 5 -> null

输出:5 -> 4 -> 3 -> 2 -> 1 -> null

解法一:迭代法(双指针)

思路

定义两个指针 prev 和 curr。prev 指向已反转部分的头(初始为null),curr 指向当前待反转节点。每次将

curr.next 指向 prev,然后两个指针同时前进。

图解

dart 复制代码
初始: null   1 -> 2 -> 3 -> null
      prev  curr

第1步: null <- 1   2 -> 3 -> null
            prev  curr

第2步: null <- 1 <- 2   3 -> null
                  prev  curr

最终: null <- 1 <- 2 <- 3
                    prev

Python代码

dart 复制代码
class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

def reverseList(head: ListNode) -> ListNode:
    prev = None
    curr = head
    while curr:
        next_temp = curr.next  # 保存下一个节点
        curr.next = prev       # 反转指向
        prev = curr            # prev前进
        curr = next_temp       # curr前进
    return prev

C++代码

dart 复制代码
struct ListNode {
    int val;
    ListNode *next;
    ListNode() : val(0), next(nullptr) {}
    ListNode(int x) : val(x), next(nullptr) {}
    ListNode(int x, ListNode *next) : val(x), next(next) {}
};

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* prev = nullptr;
        ListNode* curr = head;
        while (curr) {
            ListNode* nextTemp = curr->next;
            curr->next = prev;
            prev = curr;
            curr = nextTemp;
        }
        return prev;
    }
};

解法二:递归法

思路

递归到链表末尾,然后逐层反转 head.next.next = head,同时将 head.next 设为null。

图解

dart 复制代码
递归到底: 1 -> 2 -> 3 -> null  (head=3, 返回3)
回溯:     1 -> 2 <- 3    (2.next.next = 2)
最终: null <- 1 <- 2 <- 3

Python代码

dart 复制代码
def reverseList(head: ListNode) -> ListNode:
    if not head or not head.next:
        return head
    new_head = reverseList(head.next)
    head.next.next = head
    head.next = None
    return new_head

C++代码

dart 复制代码
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if (!head || !head->next) return head;
        ListNode* newHead = reverseList(head->next);
        head->next->next = head;
        head->next = nullptr;
        return newHead;
    }
};

相交链表

题目描述

给你两个单链表的头节点 headA 和 headB,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null。

示例:

A: 4 -> 1 -> 8 -> 4 -> 5 B: 5 -> 0 -> 1 -> 8 -> 4 -> 5 相交于节点 8。

解法一:长度对齐法

思路

先遍历两个链表得到长度,让长的链表先走差值步,然后两个指针同步前进,第一个相等的节点即为交点。

图解

dart 复制代码
A: 4 -> 1 -> 8 -> 4 -> 5   (len=5)
B: 5 -> 0 -> 1 -> 8 -> 4 -> 5 (len=6)
B先走1步,然后同步走:
A:      1 -> 8...
B:      1 -> 8...
相遇于8

Python代码

dart 复制代码
def getIntersectionNode(headA, headB):
    def get_length(head):
        length = 0
        while head:
            length += 1
            head = head.next
        return length
    
    lenA, lenB = get_length(headA), get_length(headB)
    while lenA > lenB:
        headA = headA.next
        lenA -= 1
    while lenB > lenA:
        headB = headB.next
        lenB -= 1
    
    while headA != headB:
        headA = headA.next
        headB = headB.next
    return headA

C++代码

dart 复制代码
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        int lenA = 0, lenB = 0;
        ListNode *pA = headA, *pB = headB;
        while (pA) { lenA++; pA = pA->next; }
        while (pB) { lenB++; pB = pB->next; }
        
        pA = headA; pB = headB;
        if (lenA > lenB) {
            for (int i = 0; i < lenA - lenB; ++i) pA = pA->next;
        } else {
            for (int i = 0; i < lenB - lenA; ++i) pB = pB->next;
        }
        
        while (pA && pB) {
            if (pA == pB) return pA;
            pA = pA->next;
            pB = pB->next;
        }
        return nullptr;
    }
};

解法二:双指针循环相遇法(更优雅)

思路

两个指针分别从 headA 和 headB

开始遍历,当指针走到末尾时,切换到另一个链表的头部继续。如果相交,则两个指针会在相交点相遇;如果不相交,则同时变为 null。

Python代码

dart 复制代码
def getIntersectionNode(headA, headB):
    if not headA or not headB:
        return None
    pA, pB = headA, headB
    while pA != pB:
        pA = pA.next if pA else headB
        pB = pB.next if pB else headA
    return pA

C++代码

dart 复制代码
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        if (!headA || !headB) return nullptr;
        ListNode *pA = headA, *pB = headB;
        while (pA != pB) {
            pA = pA ? pA->next : headB;
            pB = pB ? pB->next : headA;
        }
        return pA;
    }
};

移除链表元素

题目描述

给你一个链表的头节点 head 和一个整数 val,请你删除链表中所有满足 Node.val == val 的节点,并返回新的头节点。

示例:

dart 复制代码
输入:1 -> 2 -> 6 -> 3 -> 4 -> 5 -> 6, val = 6
输出:1 -> 2 -> 3 -> 4 -> 5

解法:虚拟头结点

思路

因为头结点也可能被删除,所以引入一个虚拟头结点 dummy,其 next 指向 head。然后遍历链表,删除目标节点。

图解

dart 复制代码
dummy -> 1 -> 2 -> 6 -> 3 -> ...   val=6
遍历到2时,检查2.next.val == 6,则 2.next = 2.next.next
dummy -> 1 -> 2 -> 3 -> ...

Python代码

dart 复制代码
def removeElements(head: ListNode, val: int) -> ListNode:
    dummy = ListNode(next=head)
    prev = dummy
    curr = head
    while curr:
        if curr.val == val:
            prev.next = curr.next
        else:
            prev = curr
        curr = curr.next
    return dummy.next

C++代码

dart 复制代码
class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        ListNode* dummy = new ListNode(0, head);
        ListNode* prev = dummy;
        ListNode* curr = head;
        while (curr) {
            if (curr->val == val) {
                prev->next = curr->next;
                delete curr;
                curr = prev->next;
            } else {
                prev = curr;
                curr = curr->next;
            }
        }
        ListNode* newHead = dummy->next;
        delete dummy;
        return newHead;
    }
};

删除链表的倒数第 N 个结点

题目描述

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

示例:

输入:1 -> 2 -> 3 -> 4 -> 5, n = 2

输出:1 -> 2 -> 3 -> 5

解法:快慢指针(间隔 N)

思路

使用快慢指针,快指针先走 n 步,然后快慢指针同步移动,当快指针到达末尾时,慢指针正好在倒数第 n 个节点的前一个位置。

注意:需要虚拟头结点处理删除头结点的情况。

图解

dart 复制代码
head: 1 -> 2 -> 3 -> 4 -> 5, n=2
dummy -> 1 -> 2 -> 3 -> 4 -> 5
fast先走2步:fast指向2
然后同步走:
slow指向dummy, fast指向2
...直到fast.next为空,此时slow指向3,删除3后面的4

Python代码

dart 复制代码
def removeNthFromEnd(head: ListNode, n: int) -> ListNode:
    dummy = ListNode(next=head)
    fast = dummy
    slow = dummy
    # fast 先走 n+1 步(因为要删除slow.next)
    for _ in range(n + 1):
        fast = fast.next
    while fast:
        fast = fast.next
        slow = slow.next
    slow.next = slow.next.next
    return dummy.next

C++代码

dart 复制代码
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* dummy = new ListNode(0, head);
        ListNode* fast = dummy;
        ListNode* slow = dummy;
        for (int i = 0; i <= n; ++i) {
            fast = fast->next;
        }
        while (fast) {
            fast = fast->next;
            slow = slow->next;
        }
        ListNode* toDelete = slow->next;
        slow->next = slow->next->next;
        delete toDelete;
        ListNode* newHead = dummy->next;
        delete dummy;
        return newHead;
    }
};

两两交换链表中的节点

题目描述

给你一个链表,两两交换其中相邻的节点,并返回交换后的链表。

示例:

输入:1 -> 2 -> 3 -> 4

输出:2 -> 1 -> 4 -> 3

解法一:迭代法(指针操作)

思路

使用虚拟头结点,三个指针:prev(交换对的前一个节点)、first、second。交换 first 和 second

的位置,然后整体前移。

图解

dart 复制代码
dummy -> 1 -> 2 -> 3 -> 4
prev指向dummy,first=1,second=2
交换后:dummy -> 2 -> 1 -> 3 -> 4
然后prev移动到1,继续处理3和4

Python代码

dart 复制代码
def swapPairs(head: ListNode) -> ListNode:
    dummy = ListNode(next=head)
    prev = dummy
    while prev.next and prev.next.next:
        first = prev.next
        second = first.next
        # 交换
        prev.next = second
        first.next = second.next
        second.next = first
        # 移动prev
        prev = first
    return dummy.next

C++代码

dart 复制代码
class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        ListNode* dummy = new ListNode(0, head);
        ListNode* prev = dummy;
        while (prev->next && prev->next->next) {
            ListNode* first = prev->next;
            ListNode* second = first->next;
            prev->next = second;
            first->next = second->next;
            second->next = first;
            prev = first;
        }
        ListNode* newHead = dummy->next;
        delete dummy;
        return newHead;
    }
};

解法二:递归法

思路

递归交换每两个节点。head 和 head.next 交换,然后 head.next.next 指向后续交换后的结果。

Python代码

dart 复制代码
def swapPairs(head: ListNode) -> ListNode:
    if not head or not head.next:
        return head
    new_head = head.next
    head.next = swapPairs(new_head.next)
    new_head.next = head
    return new_head

C++代码

dart 复制代码
class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        if (!head || !head->next) return head;
        ListNode* newHead = head->next;
        head->next = swapPairs(newHead->next);
        newHead->next = head;
        return newHead;
    }
};

环形链表(判环)

题目描述

给你一个链表的头节点 head,判断链表中是否有环。

示例:

环形链表返回 true,否则 false。

解法:快慢指针

思路

定义快指针 fast 每次走两步,慢指针 slow 每次走一步。如果链表有环,快慢指针最终会在环内相遇;如果没有环,快指针会先到达

null。

图解

dart 复制代码
1 -> 2 -> 3 -> 4
     ^         |
     |_________|
slow: 1,2,3,4,2...
fast: 1,3,2,4,3...
在3处相遇

Python代码

dart 复制代码
def hasCycle(head: ListNode) -> bool:
    if not head or not head.next:
        return False
    slow = head
    fast = head.next
    while slow != fast:
        if not fast or not fast.next:
            return False
        slow = slow.next
        fast = fast.next.next
    return True

C++代码

dart 复制代码
class Solution {
public:
    bool hasCycle(ListNode *head) {
        if (!head || !head->next) return false;
        ListNode* slow = head;
        ListNode* fast = head;
        while (fast && fast->next) {
            slow = slow->next;
            fast = fast->next->next;
            if (slow == fast) return true;
        }
        return false;
    }
};

环形链表 II(找入环点)

题目描述

给定一个链表的头节点 head,返回链表开始入环的第一个节点。如果无环,返回 null。

解法:快慢指针 + 数学推导

思路

思路

  1. 先用快慢指针判断是否有环,相遇点记为 meet。
  2. 将其中一个指针放回头结点,两个指针同步每次走一步,再次相遇的点就是入环点。

数学原理:

设头结点到入环点距离为 a,入环点到相遇点距离为 b,相遇点到入环点(沿环方向)距离为 c。快指针走过的距离是慢指针的两倍,可得 a = c。

图解

dart 复制代码
head -> (a) -> 入环点 -> (b) -> 相遇点 -> (c) -> 回环
a = c,所以从头和相遇点同时走,会在入环点相遇

Python代码

dart 复制代码
def detectCycle(head: ListNode) -> ListNode:
    slow = fast = head
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next
        if slow == fast:
            # 有环,找入环点
            slow = head
            while slow != fast:
                slow = slow.next
                fast = fast.next
            return slow
    return None

C++代码

dart 复制代码
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode *slow = head, *fast = head;
        while (fast && fast->next) {
            slow = slow->next;
            fast = fast->next->next;
            if (slow == fast) {
                slow = head;
                while (slow != fast) {
                    slow = slow->next;
                    fast = fast->next;
                }
                return slow;
            }
        }
        return nullptr;
    }
};

两数相加

题目描述

给你两个非空的链表,表示两个非负的整数。它们每位数字按照逆序方式存储,并且每个节点只能存储一位数字。请你将两个数相加,并以相同形式返回一个表示和的链表。

示例:

输入:l1 = [2,4,3] (表示342),l2 = [5,6,4] (表示465)

输出:[7,0,8] (表示807)

解释:342 + 465 = 807,逆序输出。

解法:模拟加法 + 进位处理

思路

同时遍历两个链表,对应节点值相加,加上进位 carry,新节点值为 sum % 10,进位更新为 sum //

10。遍历结束后如果进位不为0,则再添加一个节点。

图解

dart 复制代码
l1: 2 -> 4 -> 3
l2: 5 -> 6 -> 4
carry=0
2+5+0=7 -> 7, carry=0
4+6+0=10 -> 0, carry=1
3+4+1=8 -> 8, carry=0
结果: 7 -> 0 -> 8

Python代码

dart 复制代码
def addTwoNumbers(l1: ListNode, l2: ListNode) -> ListNode:
    dummy = ListNode()
    cur = dummy
    carry = 0
    while l1 or l2 or carry:
        val1 = l1.val if l1 else 0
        val2 = l2.val if l2 else 0
        total = val1 + val2 + carry
        carry = total // 10
        cur.next = ListNode(total % 10)
        cur = cur.next
        if l1: l1 = l1.next
        if l2: l2 = l2.next
    return dummy.next

C++代码

dart 复制代码
class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        ListNode* dummy = new ListNode(0);
        ListNode* cur = dummy;
        int carry = 0;
        while (l1 || l2 || carry) {
            int val1 = l1 ? l1->val : 0;
            int val2 = l2 ? l2->val : 0;
            int sum = val1 + val2 + carry;
            carry = sum / 10;
            cur->next = new ListNode(sum % 10);
            cur = cur->next;
            if (l1) l1 = l1->next;
            if (l2) l2 = l2->next;
        }
        ListNode* res = dummy->next;
        delete dummy;
        return res;
    }
};

🎯 总结

链表题的核心在于画图和指针指向的调整。多画几次图,多跑几个测试用例,很快就能掌握。

相关推荐
流年如夢1 小时前
顺序表 -->增、删、查、改等详细操作
c语言·数据结构
wilbertzhou2 小时前
华为4A架构中的信息架构设计方法:从数据资源到战略资产的治理之道
数据结构·togaf·企业架构·4a架构
小年糕是糕手2 小时前
【C/C++刷题集】栈、stack、队列、queue核心精讲
c语言·开发语言·数据结构·数据库·c++·算法·蓝桥杯
念恒123062 小时前
python(环境安装,输入输出,变量)
python·学习
始三角龙2 小时前
LeetCode hoot 100 -- 最小覆盖子串
算法·leetcode·职场和发展
小年糕是糕手2 小时前
【C/C++刷题集】顺序表、vector、链表、list核心精讲
c语言·开发语言·数据结构·c++·算法·leetcode·蓝桥杯
你数过天上的星星吗2 小时前
Python学习笔记一(标识符、关键字、变量、数据类型、关系运算)
笔记·python·学习
水木流年追梦2 小时前
CodeTop Top 300 热门题目10-验证IP地址
python·网络协议·tcp/ip·算法·leetcode
样例过了就是过了2 小时前
LeetCode热题100 乘积最大子数组
c++·算法·leetcode·动态规划