leetcode-链表

这篇博客详细介绍了LeetCode中涉及链表的多个问题,包括删除倒数第N个节点、合并两个有序链表、合并K个升序链表、两两交换链表节点、旋转链表、删除排序链表中的重复元素、分隔链表、反转链表、K个一组翻转链表、复制带随机指针的链表、环形链表问题、对链表进行插入排序、相交链表、回文链表、奇偶链表以及两个链表相加等。每个问题都有问题描述、解析和代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

List

链表结构

如无特殊说明,链表结构如下:

// Definition for singly-linked list.
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) {}
}

链表模板1

链表的创建,打印,销毁。

#include <iostream>
#include <vector>

using namespace std;

struct ListNode {
    int val;
    ListNode* next;
    ListNode():val(0),next(nullptr) {}
    ListNode(int v):val(v),next(nullptr) {}
    ListNode(int v, ListNode* p):val(0),next(p) {}
};

ListNode* createList(const vector<int>& nums)
{
    ListNode dummy(0);
    ListNode* node = &dummy;

    for (const int& n: nums)
    {
        node->next = new ListNode(n);
        node = node->next;
    }

    return dummy.next;
}

void printList(ListNode* head)
{
    while (head)
    {
        cout << head->val << " ";
        head = head->next;
    }
    cout << endl;
}

void destroyList(ListNode* head)
{
    while (head)
    {
        ListNode* delNode = head;
        head = head->next;
        delete delNode;
    }
}

int main(int argc, char* argv[])
{
    vector<vector<int>> nums{{}, {1}, {1,2,3,4,5,6}};

    for (auto& n: nums)
    {
        cout << "---------------" << endl;
        ListNode* head = createList(n);
        printList(head);

        // ... do something here
        printList(head);

        destroyList(head);
    }
    return 0;
}

链表模板2

输入是字符串如:“1->2->3->4”,根据字符串创建链表。

#include <iostream>
#include <vector>

using namespace std;

struct ListNode {
    int val;
    ListNode* next;
    ListNode():val(0),next(nullptr) {}
    ListNode(int v):val(v),next(nullptr) {}
    ListNode(int v, ListNode* p):val(0),next(p) {}
};

ListNode* createList(const vector<int>& nums)
{
    ListNode dummy(0);
    ListNode* node = &dummy;

    for (const int& n: nums)
    {
        node->next = new ListNode(n);
        node = node->next;
    }

    return dummy.next;
}

void printList(ListNode* head)
{
    while (head)
    {
        cout << head->val;
        head = head->next;
        if (head)    // 注意这里的链表打印!!
            cout << "->";
    }
    cout << endl;
}

void destroyList(ListNode* head)
{
    while (head)
    {
        ListNode* delNode = head;
        head = head->next;
        delete delNode;
    }
}

// 函数参数是字符串!!
ListNode* createList(string& str)
{
    if (str == "")
        return nullptr;

    ListNode dummy(0);
    ListNode* node = &dummy;
    int i = 0;
    int n = str.size();
    int num = 0;

    while (i < n)
    {
        if (str[i] >= '0' && str[i] <= '9')
        {
            num = num * 10 + (str[i] - '0');
        }
        else if(str[i] == '-')
        {
            node->next = new ListNode(num);
            node = node->next;
            num = 0;
        }
        i++;
    }
    node->next = new ListNode(num);
    return dummy.next;
}

int main(int argc, char* argv[])
{
    string str1;
    cin >> str1; // 1->2->3

    string str2;
    cin >> str2; // 0->1->4->5

    ListNode* head1 = createList(str1);
    ListNode* head2 = createList(str2);

    // do something ...

    destroyList(head1);
    destroyList(head2);

    return 0;
}

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

问题描述:
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
要求只能使用一趟扫描。
其中:
链表中结点的数目为 sz
1 <= sz <= 30
0 <= Node.val <= 100
1 <= n <= sz
比如:

输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]

解析:

  1. 该题可以用快慢指针来实现一趟扫描找出被删除节点。只要让两个节点指针相差(n-1)个节点,然后让快的节点指针指向链表的最后一个节点,那么慢的节点指针自然指向要删除的那个节点。
  2. 注意被删除节点为头节点的情况,也就是n == sz的情况,需要设置哨兵节点。
  3. 让两个快慢两个指针的差距为n,可以减少一个prevNode的变量。

代码实现:

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode dummy(0);
        dummy.next = head;
        ListNode* slow = &dummy;
        ListNode* fast = slow;

        for (int i = 0; i < n; i++)
            fast = fast->next;
        
        while (fast->next)
        {
            slow = slow->next;
            fast = fast->next;
        }

        ListNode* temp = slow->next->next;
        delete slow->next;
        slow->next = temp;

        return dummy.next;
    }
};

21. 合并两个有序链表

问题描述:
将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例1:
在这里插入图片描述

输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]

解析:

  1. 总体思路是:每次比较链表l1和链表l2的值,那个小,就将该节点合入新链表中,然后继续拿小节点的next节点和之前大节点进行比较,直到其中一个链表为空。
  2. 定义一个虚拟节点,取值为INT_MIN,确保在排序之后,始终是第一个节点,那这样的话,虚拟节点的next节点就是真正的新链表指针。

代码实现:

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        ListNode dummy(0);
        ListNode* node = &dummy;

        while (l1 && l2)
        {
            if (l1->val < l2->val)
            {
                node->next = l1;
                l1 = l1->next;
            }
            else
            {
                node->next = l2;
                l2 = l2->next;
            }
            node = node->next;
        }

        node->next = l1?l1:l2;

        return dummy.next;
    }
};

23. 合并K个升序链表

问题描述:
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
示例:

输入:lists = [[1,4,5],[1,3,4],[2,6]]
输出:[1,1,2,3,4,4,5,6]
解释:链表数组如下:
[
  1->4->5,
  1->3->4,
  2->6
]
将它们合并到一个有序链表中得到。
1->1->2->3->4->4->5->6

解析:

  1. 循环合并:思路跟<21. 合并两个有序链表>是一样的,不过需要设置循环进行两两合并。
  2. 分治合并,先两两合并,再两两合并 …
    在这里插入图片描述
    代码实现:
    循环合并:
class Solution {
private:
    ListNode* mergeTwoList(ListNode* root1, ListNode* root2)
    {
        ListNode dummy(0);
        ListNode* node = &dummy;

        while (root1 && root2)
        {
            if (root1->val < root2->val)
            {
                node->next = root1;
                root1 = root1->next;
            }
            else
            {
                node->next = root2;
                root2 = root2->next;
            }
            node = node->next;
        }

        node->next = root1?root1:root2;

        return dummy.next;
    }
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        ListNode* ret = nullptr;

        for (int i = 0; i < lists.size(); i++)
        {
            ret = mergeTwoList(ret, lists[i]);
        }

        return ret;
    }
};

分治合并:

class Solution {
private:
    ListNode* mergeTwoList(ListNode* root1, ListNode* root2)
    {
        ListNode dummy(0);
        ListNode* node = &dummy;

        while (root1 && root2)
        {
            if (root1->val < root2->val)
            {
                node->next = root1;
                root1 = root1->next;
            }
            else
            {
                node->next = root2;
                root2 = root2->next;
            }
            node = node->next;
        }

        node->next = root1?root1:root2;

        return dummy.next;
    }
    ListNode* mergeThem(vector<ListNode*>& lists, int l, int r)
    {
        if (l == r) // 关键!!索引相同,直接返回一个链表
            return lists[l];
        
        if (l > r) // 关键!!左索引大于右索引,返回nullptr
            return nullptr;
        
        int m = l + ((r - l) >> 1); 
        ListNode* left = mergeThem(lists, l, m); // 一直递归左边
        ListNode* right = mergeThem(lists, m + 1, r); //一直递归右边

        return mergeTwoList(left, right);
    }
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        return mergeThem(lists, 0, (int)lists.size() - 1);
    }
};

24. 两两交换链表中的节点

问题描述:
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
示例:
在这里插入图片描述

输入:head = [1,2,3,4]
输出:[2,1,4,3]

解析:

  1. 为了操作方便,创建一个虚拟节点,其next指针指向链表头节点,待两两交换后,其next指针仍然指向链表头节点。
  2. 步骤:
    在这里插入图片描述
    代码实现:
class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        ListNode dummy(0);
        dummy.next = head;
        ListNode* prevNode = &dummy;
        ListNode* currNode = head;

        while (currNode && currNode->next)
        {
        	// 注意赋值的是next...
            prevNode->next = currNode->next;
            currNode->next = currNode->next->next;
            prevNode->next->next = currNode;

            prevNode = currNode;
            currNode = currNode->next;
        }

        return dummy.next;
    }
};

61. 旋转链表

问题描述:
给你一个链表的头节点 head ,旋转链表,将链表每个节点向右移动 k 个位置。
示例:
在这里插入图片描述

输入:head = [1,2,3,4,5], k = 2
输出:[4,5,1,2,3]

解析:

  1. 首先遍历链表,获得链表节点数nodeCnt。
  2. 计算step = nodeCnt - k%nodeCnt,得到从最后一个节点移动到目的节点的步数。
  3. 将链表成环,并移动step步,则当前节点的下一个节点为新的头节点。
  4. 断环。

代码实现:
尾插法: – 需要不断断链,接链

class Solution {
public:
    ListNode* rotateRight(ListNode* head, int k) {
        if (head == nullptr || head->next == nullptr)
            return head;

        ListNode dummy(0);
        dummy.next = head;
        int n = 1;
        
        while (head->next)
        {
            head = head->next;
            n++;
        }
        k = n - (k % n);

        for (int i = 0; i < k; i++)
        {
            head->next = dummy.next;
            dummy.next = (dummy.next)->next;
            head = head->next;
            head->next = nullptr;
        }

        return dummy.next;
    }
};

成环法:

class Solution {
public:
    ListNode* rotateRight(ListNode* head, int k) {
        if (head == nullptr || head->next == nullptr || k == 0)
            return head;

        ListNode* node = head;
        int n = 1;

        // 计算节点数
        while (node->next)
        {
            node = node->next;
            n++;
        }
        k = n - (k % n);

        // 成环
        node->next = head;

        // 特定位置断环
        for (int i = 0; i < k; i++)
            node = node->next;
        head = node->next;
        node->next = nullptr;

        return head;
    }
};

82. 删除排序链表中的重复元素 II

问题描述:
存在一个按升序排列的链表,给你这个链表的头节点 head ,请你删除链表中所有存在数字重复情况的节点,只保留原始链表中没有重复出现的数字。返回同样按升序排列的结果链表。
示例1:
在这里插入图片描述

输入:head = [1,2,3,3,4,4,5]
输出:[1,2,5]

示例2:
在这里插入图片描述

输入:head = [1,1,1,2,3]
输出:[2,3]

解析:

  1. 创建一个哨兵节点,其next指针指向给定链表的头节点。
  2. 如果下一个结点的值和当前结点的值相等,则循环向后遍历直到找到一个和当前结点值不相等的结点;
  3. 反之,如果下一个结点的值和当前结点的值不相等,此值即为原始链表中没有重复出现的数字,将其加入到新链表中。

代码实现:

class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
        ListNode dummy(0);// 新建哨兵节点,注意这里取值不能用head,可能nullptr
        dummy.next = head;
        ListNode* prev = &dummy;

        while (prev->next && prev->next->next)// 注意这里的条件
        {
            if (prev->next->val == prev->next->next->val)
            {
                int num = prev->next->val;
                while (prev->next && num == prev->next->val)
                {
                    ListNode* temp = prev->next->next;
                    delete prev->next;
                    prev->next = temp;
                }
            }
            else // 不相等才需要迭代!
            {
                prev = prev->next;
            }
        }
        return dummy.next;
    }
};

86. 分隔链表

问题描述:
给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,使得所有小于 x 的节点都出现在大于或等于 x的节点之前。
你应当保留两个分区中每个节点的初始相对位置。
示例1:
在这里插入图片描述

输入:head = [1,4,3,2,5,2], x = 3
输出:[1,2,2,4,3,5]

解析:
分别创建两个链表,遍历给定链表,将小于x的节点和大于等于x的节点分别放到两个链表中,最后将两个链表合并既可。

代码实现:

class Solution {
public:
    ListNode* partition(ListNode* head, int x) {
        ListNode lHead(0);
        ListNode rHead(0);
        ListNode* lNode = &lHead;
        ListNode* rNode = &rHead;
        
        while (head)
        {
            if (head->val < x)
            {
                lNode->next = head;
                lNode = lNode->next;
            }
            else
            {
                rNode->next = head;
                rNode = rNode->next;
            }
            head = head->next;
        }
        lNode->next = rHead.next;
        rNode->next = nullptr; // 注意这里需要置为nullptr !!
        
        return lHead.next;
    }
};

92. 反转链表 II

问题描述:
给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回反转后的链表
示例1:
在这里插入图片描述

输入:head = [1,2,3,4,5], left = 2, right = 4
输出:[1,4,3,2,5]

解析:

  1. 前插法,过程如下图:
    在这里插入图片描述
    代码实现:
class Solution {
public:
    ListNode* reverseBetween(ListNode* head, int left, int right) {
        ListNode dummy(0);
        dummy.next = head;
        ListNode* prev = &dummy;
        ListNode* last = nullptr;

        // prev 和 last 固定不变,
        // 分别表示(left - 1)个节点和待移动位置节点的前一个节点。
        // 然后再prev和prev->next中间插入新节点。
        for (int i = 1; i < left; i++)
            prev = prev->next;
        last = prev->next;

        for (int i = 0; i < right - left; i++)
        {
            ListNode* move = last->next;
            last->next = move->next;
            move->next = prev->next;
            prev->next = move;
        }

        return dummy.next;
    }
};

25. K 个一组翻转链表

问题描述:
给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。
k 是一个正整数,它的值小于或等于链表的长度。
如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。
示例:
在这里插入图片描述

输入:head = [1,2,3,4,5], k = 2
输出:[2,1,4,3,5]

解析:
模拟…
代码实现:

class Solution {
public:
    ListNode* reverseList(ListNode* head)
    {
        ListNode* prev = nullptr;
        ListNode* curr = head;

        while (curr)
        {
            ListNode* temp = curr->next;
            curr->next = prev;

            prev = curr;
            curr = temp;
        }

        return prev;
    }

    ListNode* reverseKGroup(ListNode* head, int k) {
        if (head == nullptr || head->next == nullptr || k < 2)
            return head;

        ListNode dummy(0);
        dummy.next = head;
        ListNode* prev = &dummy;
        ListNode* newHead = head;

        while (newHead)
        {
            for (int i = 1; i < k; i++)
            {
                newHead = newHead->next;
                if (newHead == nullptr)
                    return dummy.next;
            }

            ListNode* temp = newHead->next;
            newHead->next = nullptr;
            newHead = prev->next;

            prev->next = reverseList(newHead);
            newHead->next = temp;

            prev = newHead;
            newHead = temp;
        }

        return dummy.next;
    }
};

138. 复制带随机指针的链表

问题描述:
给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。
构造这个链表的 深拷贝。 深拷贝应该正好由 n 个全新节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。
示例:
在这里插入图片描述

输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]

解析:

  1. 构造新链表
    遍历原来的链表并拷贝每一个节点,将拷贝节点放在原来节点的旁边,创造出一个旧节点和新节点交错的链表。如图:
    在这里插入图片描述
    拷贝节点构造新链表:
    在这里插入图片描述
  2. 给拷贝节点的random赋值
    迭代这个新旧节点交错的链表,并用旧节点的 random 指针去更新对应新节点的 random 指针。比方说, B 的 random 指针指向 A ,意味着 B’ 的 random 指针指向 A’ 。
    在这里插入图片描述
  3. 现在 random 指针已经被赋值给正确的节点, next 指针也需要被正确赋值,以便将新的节点正确链接同时将旧节点重新正确链接。
    在这里插入图片描述

代码实现:

class Solution {
public:
    Node* copyRandomList(Node* head) {
        if (head == nullptr)
            return head;
        
        Node* tempNode = head;
        Node* currNode = head;

        while (currNode != nullptr)
        {
            tempNode = currNode->next;
            currNode->next = new Node(currNode->val);
            currNode->next->next = tempNode;
            currNode = tempNode;
        }

        currNode = head;
        while (currNode != nullptr)
        {
            tempNode = currNode->next;// clone node
            if (currNode->random != nullptr)
                tempNode->random = currNode->random->next;
            currNode = tempNode->next;
        }

        currNode = head;
        Node* copyList = tempNode = head->next;
        while (tempNode != nullptr && tempNode->next != nullptr)
        {
            currNode->next = currNode->next->next;
            tempNode->next = tempNode->next->next;
            currNode = currNode->next;
            tempNode = tempNode->next;
        }
        currNode->next = nullptr;

        return copyList;
    }
};

142. 环形链表 II

问题描述:
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意,pos 仅仅是用于标识环的情况,并不会作为参数传递到函数中。
说明:不允许修改给定的链表。
示例:
在这里插入图片描述

输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。

解析:

  1. 通过快慢指针是否相遇得知链表中是否有环。
  2. 非环节点数有a个,环中节点数有b个,快指针走了f步,慢指针走了s步。
    那么有如下关系:
    1> f = 2s (快指针每次走2步,慢指针每次走1步,快路程是慢路程的2倍)
    2> f = s + nb (快指针和慢指针相遇时,快指针比慢指针多走了n个环,也就是nb步)
    上面两式相加得出:s = nb (don’t know it, just feel it…)
    从head节点走到入环点需要走:a + nb(n>=0)
    结合上面推导出的公式,需要走a + s就到入环点了。
    也就是说:slow只需要走a步就是入环节点了。
    如何知道slow刚好走了a步呢?只需要从head开始,和slow指针一起走,相遇时刚好就是a步了。

代码实现:

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
    	// slow 和fast一开始要相等!!
        ListNode* slow = head;
        ListNode* fast = head;

        do
        {
            if (fast == nullptr || fast->next == nullptr)
                return nullptr;
            slow = slow->next;
            fast = fast->next->next;
        }
        while (slow != fast);

        slow = head;
        while (slow != fast)
        {
            fast = fast->next;
            slow = slow->next;
        }

        return slow;
    }
};

143. 重排链表

问题描述:
给定一个单链表 L:L0→L1→…→Ln-1→Ln ,
将其重新排列后变为: L0→Ln→L1→Ln-1→L2→Ln-2→…
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
示例:

1> 给定链表 1->2->3->4, 重新排列为 1->4->2->3.
2> 给定链表 1->2->3->4->5, 重新排列为 1->5->2->4->3.

解析:

  1. 找到原链表的中点,并断链,分为左右两个链表。
  2. 将从原链表分开的右链表反转。
  3. 将左右两个链表按照题目要求合并。

代码实现:

class Solution {
public:
    ListNode* reverseList(ListNode* head)
    {
        ListNode* prev = nullptr;
        ListNode* curr = head;

        while (curr)
        {
            ListNode* temp = curr->next;
            curr->next = prev;
            prev = curr;
            curr = temp;
        }

        return prev;
    }
    ListNode* findMidNode(ListNode* head)
    {
        if (head == nullptr)
            return nullptr;
        
        ListNode* slow = head;
        ListNode* fast = head->next;

        while (fast && fast->next)
        {
            fast = fast->next->next;
            slow = slow->next;
        }

        return slow;
    }
    void reorderList(ListNode* head) {
        if (head == nullptr || head->next == nullptr)
            return;
        
        ListNode* mid = findMidNode(head);
        ListNode* rList = reverseList(mid->next);
        mid->next = nullptr;

        ListNode dummy(0);
        ListNode* node = &dummy;
        while (head && rList)
        {
            node->next = head;
            head = head->next;
            node = node->next;

            node->next = rList;
            rList = rList->next;
            node = node->next;
        }
        node->next = head; // 注意这里需要赋值,因为左链表可能多一个节点!!
    }
};

147. 对链表进行插入排序

问题描述:
对链表进行插入排序。
在这里插入图片描述
插入排序的动画演示如上。从第一个元素开始,该链表可以被认为已经部分排序(用黑色表示)。
每次迭代时,从输入数据中移除一个元素(用红色表示),并原地将其插入到已排好序的链表中。

示例:

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

解析:
对链表进行插入排序的具体过程如下。

  1. 首先判断给定的链表是否为空,若为空,则不需要进行排序,直接返回。

  2. 创建哑节点 dummy,令 dummy.next = head。引入哑节点是为了便于在 head 节点之前插入节点。

  3. 维护 lastSortedNode为链表的已排序部分的最后一个节点,初始时 lastSortedNode = head。

  4. 维护 currNode 为待插入的元素,初始时 curr = head.next。

  5. 比较 lastSortedNode 和 currNode 的节点值。
    1)若 lastSortedNode.val <= currNode.val,说明 curr 应该位于 lastSortedNode 之后,将 lastSortedNode 后移一位,currNode 变成新的 lastSortedNode。
    2)否则,从链表的头节点开始往后遍历链表中的节点,寻找插入 currNode 的位置。令 prevNode 为插入 currNode 的位置的前一个节点,进行如下操作,完成对 currNode 的插入:
    lastSortedNode.next = currNode.next
    currNode.next = prevNode.next
    prevNode.next = currNode

  6. 令 currNode = lastSortedNode.next,此时 currNode 为下一个待插入的元素。

  7. 重复第 5 步和第 6 步,直到 currNode 变成空,排序结束。

  8. 返回 dummy.next,为排序后的链表的头节点。

代码实现:

class Solution {
public:
    ListNode* insertionSortList(ListNode* head) {
        ListNode dummy(INT_MIN);
        dummy.next = head;
        ListNode* last = head;

        while (last && last->next)
        {
            if (last->next->val >= last->val)
            {
                last = last->next;
            }
            else
            {
                ListNode* move = last->next;
                last->next = last->next->next; // 注意这里是last->next!!

                ListNode* node = &dummy;
                while (move->val > node->next->val)
                    node = node->next;

                move->next = node->next;
                node->next = move;
            }
        }

        return dummy.next;
    }
};

160. 相交链表

问题描述:
编写一个程序,找到两个单链表相交的起始节点。
如下面的两个链表:
在这里插入图片描述

在节点c1开始相交

示例1:
在这里插入图片描述

输入:
intersectVal = 8		-->相交节点的值为 8
listA = [4,1,8,4,5]
listB = [5,0,1,8,4,5],
skipA = 2					-->在 A 中,相交节点前有 2 个节点
skipB = 3					-->在 B 中,相交节点前有 3 个节点。
输出:Reference of the node with value = 8

示例2:
在这里插入图片描述

输入:
intersectVal = 0		-->没有相交节点
listA = [2,6,4]
listB = [1,5]
skipA = 3					-->任意值
skipB = 2					-->任意值
输出:null				-->这两个链表不相交,因此返回 null。

解析:

  1. 设置两指针pA和pB分别指向两个给定链表,并遍历。
  2. 当到达链表末尾时,将指针指向另外一个链表的头节点。当 pA到达链表的尾部时,将它重定位到链表 B 的头节点; 类似的,当 pB到达链表的尾部时,将它重定位到链表 A 的头节点。
  3. 若在某一时刻 pA和 pB相遇,则 pA/pB为相交结点,否则,两者皆为nullptr。
  4. 为什么呢?
    1> 假设pA和pB走完了全程:
    由于指针pA走了个A+B,指针pB走了个B+A,因此两者走过的长度是相等的
    A和B结尾部分是相等的,因此A+B和B+A结尾部分也是相等的
    因此当pA与pB相遇时,该节点就是相交节点
    假设A和B没有交点
    2> A和B均是以null结尾,因此A+B和B+A也是以null结尾
    因此当pA和pB恰好走完全程后,他们同时到达null,此时他们是相等的
    依然满足循环终止条件

代码实现:

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        if (headA == nullptr || headB == nullptr)
            return nullptr;

        ListNode* nodeA = headA;
        ListNode* nodeB = headB;

        while (nodeA != nodeB)
        {
            if (nodeA == nullptr) // nodeA == nullptr,而不是nodeA->next == nullptr !!
                nodeA = headB;
            else
                nodeA = nodeA->next;
            
            if (nodeB == nullptr)
                nodeB = headA;
            else
                nodeB = nodeB->next;
        }
        // 从循环出来的话,有两种情况:
        // 1. 两个指针都指向同一个节点
        // 2. 两个指针指向nullptr...

        return nodeA;
    }
};

or:

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        if (headA == nullptr || headB == nullptr)
            return nullptr;
        
        ListNode* nodeA = headA;
        ListNode* nodeB = headB;

        while (nodeA || nodeB)
        {
            if (nodeA == nodeB)
                return nodeA;
            
            if (nodeA == nullptr)
                nodeA = headB;
            else
                nodeA = nodeA->next;
            
            if (nodeB == nullptr)
                nodeB = headA;
            else
                nodeB = nodeB->next;
        }

        return nullptr;
    }
};

234. 回文链表

问题描述:
请判断一个链表是否为回文链表。
示例:

1.
输入: 1->2
输出: false
2.
输入: 1->2->2->1
输出: true
3.
输入: 1
输出: true

解析:

  1. 时间复杂度为O(n),空间复杂度为O(n)的方法是创建一个数组,将链表中的值依次填写进去,然后判断数值的值是否回环。
  2. 时间复杂度为O(n),空间复杂度为O(1)的方法是找出链表中点,将中点左半边的链表反转,然后判断左右两边链表节点的值是否相同。注意判断完之后再反转回来,还原链表

代码实现:
时间复杂度为O(n),空间复杂度为O(n)

class Solution {
public:
    bool isPalindrome(ListNode* head) {
        if (head == nullptr)
            return false;
        
        vector<int> iVec;
        ListNode* currNode = head;

        while (currNode != nullptr)
        {
            iVec.push_back(currNode->val);
            currNode = currNode->next;
        }

        for (int i = 0, j = iVec.size()-1; i < j; i++, j--)
        {
            if (iVec[i] != iVec[j])
                return false;
        }

        return true;
    }
};

时间复杂度为O(n),空间复杂度为O(1)

class Solution {
public:
    ListNode* findMiddleNode (ListNode* head)
    {
        if (head == nullptr)
            return head;

        ListNode* slow = head;
        ListNode* fast = head->next;

        while (fast && fast->next)
        {
            slow = slow->next;
            fast = fast->next->next;
        }

        return slow;
    }
    ListNode* reverseList(ListNode* head)
    {
        ListNode* prev = nullptr;
        ListNode* curr = head;

        while (curr)
        {
            ListNode* temp = curr->next;
            curr->next = prev;

            prev = curr;
            curr = temp;
        }

        return prev;
    }
    bool isPalindrome(ListNode* head) {
        if (head == nullptr || head->next == nullptr)
            return true;

        ListNode* mid = findMiddleNode(head);
        ListNode* rHead = reverseList(mid->next);
        ListNode* rNode = rHead;
        mid->next = nullptr;
        bool ret = true;

        while (rNode && head)
        {
            if (rNode->val != head->val)
            {
                // 不直接return,是为了将反转的链表复原
                ret = false;
                break;
            }
            
            rNode = rNode->next;
            head = head->next;
        }

        // 将反转的链表复原
        mid->next = reverseList(rHead);

        return ret;
    }
};

328. 奇偶链表

问题描述:
给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。
请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。
示例:

输入: 1->2->3->4->5->NULL
输出: 1->3->5->2->4->NULL

解析:
在这里插入图片描述

代码实现:

class Solution {
public:
    ListNode* oddEvenList(ListNode* head) {
        if (head == nullptr)
            return head;
        // head是奇数链表的头节点,evenHead是偶数链表的头节点
        ListNode* oddNode = head;
        ListNode* evenNode = head->next;
        ListNode* evenHead = evenNode;

        while (evenNode && evenNode->next)
        {
            oddNode->next  = evenNode->next;
            evenNode->next = evenNode->next->next;

            oddNode = oddNode->next;
            evenNode = evenNode->next;
        }
        oddNode->next = evenHead;
        
        return head;
    }
};

2. 两数相加

问题描述:
给你两个非空的链表,表示两个非负的整数。它们每位数字都是按照逆序的方式存储的,并且每个节点只能存储一位数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例:
在这里插入图片描述

输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807.

解析:
直接模拟计算既可,注意进位
代码实现:

class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        int res = 0;
        ListNode result(0);// 哨兵节点
        ListNode* node = &result;

        while (l1 || l2 || res)// 注意这里的条件
        {
            int a = l1?l1->val:0;
            int b = l2?l2->val:0;
            res = a + b + res;

            node->next = new ListNode(res%10);
            res = res / 10;

            l1 = l1?l1->next:nullptr;
            l2 = l2?l2->next:nullptr;
            node = node->next;
        }

        return result.next;
    }
};

445. 两数相加 II

问题描述:
给你两个 非空 链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。
你可以假设除了数字 0 之外,这两个数字都不会以零开头。
示例:

输入:(7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 8 -> 0 -> 7

解析:

  1. 逆序类型的问题,首先想到用栈…
  2. 如果要求空间复杂度为1的话,那么就需要反转链表,再计算。计算完成之后,记得将链表反转回来

代码实现:
使用栈辅助:

class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        stack<int> s1, s2;
        int flag = 0;
        ListNode *node = nullptr;

        while (l1 != nullptr)
        {
            s1.push(l1->val);
            l1 = l1->next;
        }

        while (l2 != nullptr)
        {
            s2.push(l2->val);
            l2 = l2->next;
        }

        while (!s1.empty() || !s2.empty() || flag)
        {
            int n1 = 0;
            int n2 = 0;
            if (!s1.empty())
            {
                n1 = s1.top();
                s1.pop();
            }
            if (!s2.empty())
            {
                n2 = s2.top();
                s2.pop();
            } 
            int num = n1 + n2 + flag;

            flag = num/10;
            num %= 10;
			
			// 这里有个技巧,让新创建的节点指向之前的链表,可以避免一次翻转!!
            ListNode* temp = new ListNode(num);
            temp->next = node;
            node = temp;
        }
        return node;
    }
};

不使用辅助空间:

class Solution {
public:
    ListNode* reverseList(ListNode* head)
    {
        ListNode* prev = nullptr;
        ListNode* curr = head;

        while (curr)
        {
            ListNode* temp = curr->next;
            curr->next = prev;
            prev = curr;
            curr = temp;
        }

        return prev;
    }
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
     	// 先翻转链表,使低位在前!!
        ListNode* node1 = l1 = reverseList(l1);
        ListNode* node2 = l2 = reverseList(l2);
        ListNode* result = nullptr;
        int flag = 0;

        while (node1 || node2 || flag)
        {
            int n1 = 0;
            if (node1)
            {
                n1 = node1->val;
                node1 = node1->next;
            }

            int n2 = 0;
            if (node2)
            {
                n2 = node2->val;
                node2 = node2->next;
            }

            ListNode* temp = new ListNode((n1 + n2 + flag) % 10);
            flag = (n1 + n2 + flag) / 10;
            temp->next = result;
            result = temp;
        }
		
		// 运算结束后,需要将两个链表翻转回来!!
        reverseList(l1);
        reverseList(l2);

        return result;
    }
};

148. 排序链表

问题描述:
给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。
示例:
在这里插入图片描述

输入:head = [4,2,1,3]
输出:[1,2,3,4]

解析:

  1. 使用归并排序。
  2. 该问题转化为递归+找链表中点+合并链表。

代码实现:

class Solution {
private:
    ListNode* findMiddleNode(ListNode* head)
    {
        if (head == nullptr || head->next == nullptr)
            return head;
        
        ListNode* slow = head;
        ListNode* fast = head->next;

        while (fast != nullptr && fast->next != nullptr)
        {
            slow = slow->next;
            fast = fast->next->next;
        }

        return slow;
    }

    ListNode* mergeList(ListNode* left, ListNode* right)
    {
        ListNode dummy;
        ListNode* iNode = &dummy;

        while (left != nullptr && right != nullptr)
        {
            if (left->val < right->val)
            {
                iNode->next = left;
                left = left->next;
            }
            else
            {
                iNode->next = right;
                right = right->next;
            }
            iNode = iNode->next;
        }

        iNode->next = left == nullptr?right:left;
        return dummy.next;
    }

public:
    ListNode* sortList(ListNode* head) {
        if (head == nullptr || head->next == nullptr)
            return head;
        
        // 找中点
        ListNode* midNode = findMiddleNode(head);
        ListNode* rightList = midNode->next;
        midNode->next = nullptr; // 断链!!

        // 递归
        ListNode* left  = sortList(head);
        ListNode* right = sortList(rightList);
    
        // 合并链表
        return mergeList(left, right);
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值