【算法从零到千】【59-65】链表专题


1. 两数相加

2. 两数相加https://leetcode.cn/problems/add-two-numbers/https://leetcode.cn/problems/add-two-numbers/

给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。

请你将两个数相加,并以相同形式返回一个表示和的链表。

你可以假设除了数字 0 之外,这两个数都不会以 0 开头

写法:迭代

cpp 复制代码
class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        int carry=0,all=0;
        ListNode* phead=new ListNode(0);ListNode* cur=phead;
        while(l1&&l2)
        {
            all=l1->val+l2->val+carry;
            if(all>=10){carry=1;all%=10;}
            else{carry=0;} 
            cur->next=new ListNode(all);
            cur=cur->next;
            l1=l1->next;l2=l2->next;all=0;
        }
        while(l1||l2)
        {
            if(l1){all=l1->val+carry; l1=l1->next;}
            if(l2){all=l2->val+carry; l2=l2->next;}
            if(all>=10){carry=1;all%=10;}
            else{carry=0;} 
            cur->next=new ListNode(all);all=0;
            cur=cur->next;
        }
        if(carry==1)
        {
            cur->next=new ListNode(1);
            cur=cur->next;
        }
        cur=phead->next;
        delete phead;
        return cur;
    }
};

优化写法

cpp 复制代码
class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        // 构造哑巴节点 dummy,最后返回 dummy.next, 以方便处理新链表的头节点。
        ListNode* dummy = new ListNode(0);
        ListNode* node = dummy; // node 一直会变化(前进)
        int carrier = 0; // 进位
    // 只要有没走到头的链表或者进位不为 0 就一直前进。
    while (l1 || l2 || carrier) {
        // 求和,考虑可能有链表走到头
        int sum = (l1 ? l1->val : 0) + (l2 ? l2->val : 0) + carrier;

        // 在尾部添加节点
        node->next = new ListNode(sum % 10);
        node = node->next;
        
        // 更新进位,并向两个链表尾部前进
        carrier = sum / 10;
        if (l1) l1 = l1->next;
        if (l2) l2 = l2->next;
    }

    ListNode* result = dummy->next; // 保存结果链表的头节点
    delete dummy; // 释放哑节点的内存
    return result;
}
};

2. 删除链表的倒数第N个节点

19. 删除链表的倒数第 N 个结点https://leetcode.cn/problems/remove-nth-node-from-end-of-list/https://leetcode.cn/problems/remove-nth-node-from-end-of-list/

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

写法一:双指针

cpp 复制代码
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* cur=head;ListNode* prev=nullptr;
        int size=0;
        while(cur) {size++;cur=cur->next;}
        cur=head;int aim=size-n;
        while(aim--)
        {
            prev=cur;
            cur=cur->next;
        }
        if(cur==head) return head->next;
        if(head->next)
        prev->next=cur->next;
        else return nullptr;
        return head;
    }
};

写法二:快慢指针

cpp 复制代码
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        // 由于可能会删除链表头部,用哨兵节点简化代码
        ListNode dummy{0, head};
        ListNode* left = &dummy;
        ListNode* right = &dummy;
        while (n--) {
            right = right->next; // 右指针先向右走 n 步
        }
        while (right->next) {
            left = left->next;
            right = right->next; // 左右指针一起走
        }
        // 左指针的下一个节点就是倒数第 n 个节点
        ListNode* nxt = left->next;
        left->next = left->next->next;
        delete nxt;
        return dummy.next;
    }
};

3. 排序链表

148. 排序链表https://leetcode.cn/problems/sort-list/https://leetcode.cn/problems/sort-list/

给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表

写法一:STL

cpp 复制代码
class Solution {
public:
    ListNode* sortList(ListNode* head) {
        multimap<int,ListNode*> hash;
        ListNode* cur=head;
        while(cur)
        {
            hash.insert({cur->val,cur});
            cur=cur->next;
        }
        ListNode* phead=new ListNode(0);cur=phead;
        for(auto& e:hash)
        {
            cur->next=e.second;
            cur=cur->next;
        }
        cur->next=nullptr;
        return phead->next;
    }
};

写法二:归并排序

cpp 复制代码
class Solution {
public:
    ListNode* sortList(ListNode* head) { 
        return mergeSort(head);
    }
/**
 * 对给定的链表进行归并排序
*/
ListNode* mergeSort(ListNode* head){
    // 如果链表为空或只有一个节点,无需排序直接返回
    if(!head || !head-&gt;next){
        return head;    
    }
    // 获取链表的中间节点,分别对左右子链表进行排序
    ListNode* mid = getMid(head);
    ListNode* rightSorted = mergeSort(mid-&gt;next);   // 排序右子链表
    if(mid)mid-&gt;next = nullptr;                     // 断开两段子链表
    ListNode* leftSorted = mergeSort(head);         // 排序左子链表
    return mergeTwoLists(leftSorted, rightSorted);  // 两个子链表必然有序,合并两个有序的链表
}

/**
 * 获取以head为头节点的链表中间节点
 * 如果链表长度为奇数,返回最中间的那个节点
 * 如果链表长度为偶数,返回中间靠左的那个节点
*/
ListNode* getMid(ListNode* head){
    if(!head)return head;   
    ListNode* slow = head, *fast = head-&gt;next;          // 快慢指针,慢指针初始为
    while(fast != nullptr &amp;&amp; fast-&gt;next != nullptr)     
    {
        fast = fast-&gt;next-&gt;next;    // 快指针每次移动两个节点
        slow = slow-&gt;next;          // 慢指针每次移动一个节点
    }
    return slow;    // 快指针到达链表尾部时,慢指针即指向中间节点
}

/**
 * 合并两个有序链表list1和list2
*/
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
    ListNode* dummy = new ListNode();   // 伪头节点,用于定位合并链表的头节点
    ListNode* node = dummy;             // 新链表当前的最后一个节点,初始为伪头节点
    // 直到两个链表都遍历完了,合并结束
    while(list1 != nullptr || list2 != nullptr){
        int val1 = list1 == nullptr ? 50001 : list1 -&gt; val;   // 如果链表1已经遍历完,val1取最大值,保证链表2的节点被选择到       
        int val2 = list2 == nullptr ? 50001 : list2 -&gt; val;   // 如果链表2已经遍历完,val2取最大值,保证链表1的节点被选择到 
        if(val1 &lt; val2){
            // 链表1的节点值更小,加入到合并链表,并更新链表1指向的节点
            node -&gt; next = list1;
            list1 = list1 -&gt; next;
        }else{
            // 链表2的节点值更小,加入到合并链表,并更新链表2指向的节点
            node -&gt; next = list2;
            list2 = list2 -&gt; next;
        }
        node = node -&gt; next;    // 更新合并链表当前的最后一个节点指向
    }
    return dummy -&gt; next;       // 伪头节点的下一个节点即为合并链表的头节点
}
};

4. 重排链表

4LCR 026. 重排链表https://leetcode.cn/problems/LGjMqU/https://leetcode.cn/problems/LGjMqU/

给定一个单链表 L的头节点 head ,单链表 L 表示为:

L0 → L1 → ... → Ln-1 → Ln
请将其重新排列后变为:

L0 → Ln → L1 → Ln-1 → L2 → Ln-2 → ...

不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换

写法一:STL映射

cpp 复制代码
class Solution {
public:
    void reorderList(ListNode* head) {
        if (!head->next) return; //处理边界
    map&lt;int,ListNode*&gt; index;//记录位置
    ListNode* cur=head;int i=0;
    while(cur)
    {
        index[i++]=cur;
        cur=cur-&gt;next;
    }
    i--;//保证索引正确

    cur=head;int j=1;//前后挨个插入
    while(j&lt;=i)
    {
        cur-&gt;next=index[i--];
        cur=cur-&gt;next;
        if(i!=j)//当i=j,只执行一次
        {cur-&gt;next=index[j++]; cur=cur-&gt;next;}
    }  
    cur-&gt;next=nullptr;
}
};

写法二:反转+三指针****

cpp 复制代码
class Solution {
public:
    void reorderList(ListNode* head) {
        ListNode* dummmy = new ListNode(0);
        dummmy->next = head;
        ListNode* slow = dummmy;
        ListNode* fast = dummmy;
        while (fast != nullptr && fast->next != nullptr) {
            slow = slow->next;
            fast = fast->next->next;
        }
        delete dummmy;
        dummmy = nullptr;
        ListNode* headB = slow->next;
        slow->next = nullptr;
        ListNode* p2 = reverseList(headB);
        ListNode* p1 = head;
        ListNode* p3 = nullptr;
        while (p2 != nullptr) {
            p3 = p1->next;
            p1->next = p2;
            p1 = p2;
            p2 = p3;
        }
    }
ListNode* reverseList(ListNode* head) {
    if (head == nullptr) {
        return head;
    }
    ListNode* left = nullptr;
    ListNode* cur = head;
    ListNode* right = nullptr;
    while (cur != nullptr) {
        right = cur-&gt;next;
        cur-&gt;next = left;
        left = cur;
        cur = right;
    }
    return left;
}
};

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

24. 两两交换链表中的节点https://leetcode.cn/problems/swap-nodes-in-pairs/https://leetcode.cn/problems/swap-nodes-in-pairs/

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。

写法一:迭代

cpp 复制代码
class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        if(!head||!head->next)
        return head;
        ListNode* cur=head->next;ListNode* prev=head;
        //完成前两个
        prev->next=cur->next;
        cur->next=head;head=cur;
        //后面再规律处理
        cur=prev; prev=head;ListNode* prevv=nullptr;
        while(cur->next&&cur->next->next)
        {
            //规律移动
            if(cur->next&&cur->next->next){
                prevv=cur;
                cur=cur->next->next;
                prev=prev->next->next;
            }
            //交换流程
            prev->next=cur->next;
            cur->next=prev;
            prevv->next=cur;
            //保持cur与prev的前后关系
            cur=prev; prev=prevv->next;
        }
        //如果还剩下则为一个,不用交换
        return head;
    }
};

写法二:递归

cpp 复制代码
class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        if (head == nullptr || head->next == nullptr) {
            return head;
        }
    ListNode* node1 = head;
    ListNode* node2 = head-&gt;next;
    ListNode* node3 = node2-&gt;next;

    node1-&gt;next = swapPairs(node3); // 1 指向递归返回的链表头
    node2-&gt;next = node1; // 2 指向 1

    return node2; // 返回交换后的链表头节点
}
};

6. 合并K个升序链表

23. 合并 K 个升序链表https://leetcode.cn/problems/merge-k-sorted-lists/https://leetcode.cn/problems/merge-k-sorted-lists/

给你一个链表数组,每个链表都已经按升序排列。

请你将所有链表合并到一个升序链表中,返回合并后的链表。

写法一:STL映射

cpp 复制代码
class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        if(lists.size()==0)return nullptr;
        if(lists.size()==1)return lists[0];
        //用哈希记录索引和节点地址
        multimap<int,ListNode*> nodemap;
        for(auto& e:lists)
        {
            ListNode* cur=e;
            while(cur)
            {
                nodemap.insert({cur->val,cur});
                cur=cur->next;
            }
        }
        //重组链表
        ListNode* phead=new ListNode(0);ListNode* cur=phead;
        for(auto& e:nodemap)
        {
            cur->next=e.second;
            cur=cur->next;
        }
        cur->next=nullptr;
        return phead->next;
    }
};

写法二:优先级队列(⭐⭐)

cpp 复制代码
class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        auto cmp = [](const ListNode* a, const ListNode* b) {
            return a->val > b->val; // 最小堆
        };
        priority_queue<ListNode*, vector<ListNode*>, decltype(cmp)> pq;
        for (auto head : lists) {
            if (head) {
                pq.push(head); // 把所有非空链表的头节点入堆
            }
        }
    ListNode dummy{}; // 哨兵节点,作为合并后链表头节点的前一个节点
    auto cur = &amp;dummy;
    while (!pq.empty()) { // 循环直到堆为空
        auto node = pq.top(); // 剩余节点中的最小节点
        pq.pop();
        if (node-&gt;next) { // 下一个节点不为空
            pq.push(node-&gt;next); // 下一个节点有可能是最小节点,入堆
        }
        cur-&gt;next = node; // 把 node 添加到新链表的末尾
        cur = cur-&gt;next; // 准备合并下一个节点
    }
    return dummy.next; // 哨兵节点的下一个节点就是新链表的头节点
}
};

非常重要!


7. K个一组翻转链表

25. K 个一组翻转链表https://leetcode.cn/problems/reverse-nodes-in-k-group/https://leetcode.cn/problems/reverse-nodes-in-k-group/

给你链表的头节点 head ,每 k个节点一组进行翻转,请你返回修改后的链表。

k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k的整数倍,那么请将最后剩余的节点保持原有顺序。

你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。

写法一:栈

cpp 复制代码
class Solution {
public:
    ListNode* reverseKGroup(ListNode* head, int k) {
        if (k <= 1) return head;
        stack<ListNode*> st;
        ListNode* phead=new ListNode(0);ListNode* pcur=phead;
        ListNode* cur=head;
        //统计节点数量
        int size=0;
        while(cur){cur=cur->next;size++;}
        //按k的数量依次处理
        int n=0,count=0;cur=head;
        while(count+k<=size)
        {
            while(n<k&&cur)
            {
                st.push(cur);
                cur=cur->next;
                n++;count++;
            }
            while(!st.empty())
            {
                pcur->next=st.top();
                st.pop();
                pcur=pcur->next;
            }
            n=0;
        }
        //处理末尾情况
        if(count<size) pcur->next=cur;
        else pcur->next=nullptr;
    cur=phead-&gt;next;
    delete phead;
    return cur;
}
};

写法二:递归

cpp 复制代码
class Solution {
public:
    ListNode* reverseKGroup(ListNode* head, int k) {
        ListNode *p = head;
        for(int i = 0; i < k; i++) {
            if(!p) return head;
            p = p->next;
        }
    ListNode *q = head;
    ListNode *pre = nullptr;
    while(q != p) {
        ListNode *tmp = q-&gt;next;
        q-&gt;next = pre;
        pre = q;
        q = tmp;
    }

    head-&gt;next = reverseKGroup(p, k);
    return pre;
}
};

相关推荐
重生之后端学习1 小时前
53. 最大子数组和[中等]✅
java·数据结构·算法·leetcode·职场和发展
小手cool3 小时前
使用递归对数组进行反转操作
java·数据结构·算法
码行山野赴时序归途4 小时前
从暴力到最优:三道 C 语言入门题的解法思路
c语言·开发语言·数据结构·算法·leetcode·排序算法
土司大王4 小时前
LeetCode hot100——随机链表的复制
算法·leetcode·链表
CarIise6 小时前
C语言结构体与链表
数据结构·算法
charlie1145141918 小时前
deque、list 与 forward_list:vector 之外的三个选择
开发语言·数据结构·c++·list·开源项目
动词ing8 小时前
【学习笔记】数据结构(数组滑动窗口+无重复字符的最长字串题目)
数据结构·笔记·学习
拓人间精准客8 小时前
ToB 销售获客避坑指南:如何用全维度大数据实现降本增效
大数据·数据结构·单例模式
小七在进步9 小时前
数据结构:快速排序
数据结构·算法·排序算法