LeetCode 算法: 合并 K 个升序链表 c++

原题链接🔗 :合并 K 个升序链表
难度:困难⭐️⭐️⭐️

题目

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

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

示例 1:

输入: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

示例 2:

输入:lists = \[\]

输出:\[\]

示例 3:

输入:lists = \[]

输出:\[\]

提示

  • k == lists.length
  • 0 <= k <= 104
  • 0 <= listsi.length <= 500
  • -104 <= listsij <= 104
    listsi 按 升序 排列
    listsi.length 的总和不超过 104

题解

分支法

  1. 解题思路:
  1. ListNode结构体 :定义了一个链表节点,包含一个整数值val和一个指向下一个节点的指针next

  2. Solution类:包含合并K个链表的逻辑。

    • mergeKLists函数:接收一个链表指针的向量,调用merge函数进行合并。
    • merge函数:递归地将链表列表分成两半,直到每半只有一个链表,然后调用mergeTwoLists函数合并两个链表。
    • mergeTwoLists函数:合并两个已排序的链表,返回合并后的链表头节点。
  3. 辅助函数

    • createList函数:根据提供的整数列表创建一个新的链表。
    • deleteList函数:释放链表占用的内存。
    • printList函数:打印链表的元素。
  4. main函数 :程序的入口点,用于测试Solution类的mergeKLists函数。

    • 创建了三个链表l1l2l3
    • 将这些链表放入一个向量lists中。
    • 使用Solution类的实例调用mergeKLists函数,合并链表。
    • 打印合并后的链表。
    • 释放合并后链表的内存。

使用分治法来合并链表。merge函数递归地将链表列表分成两半,直到每个子问题只包含一个链表,然后使用mergeTwoLists函数将两个相邻的链表合并。这种方法的时间复杂度为O(N log K),其中N是所有链表中节点的总数,K是链表的数量。

  1. 复杂度:时间复杂度为O(N log K),空间复杂度时间复杂度为O(log K)
  2. c++ demo:
cpp 复制代码
#include <iostream>
#include <vector>

struct ListNode {
    int val;
    ListNode* next;
    ListNode(int x) : val(x), next(nullptr) {}
};

class Solution {
public:
    ListNode* mergeKLists(std::vector<ListNode*>& lists) {
        if (lists.empty()) return nullptr;
        return merge(lists, 0, lists.size() - 1);
    }

private:
    ListNode* merge(std::vector<ListNode*>& lists, int left, int right) {
        if (left == right) return lists[left];
        int mid = left + (right - left) / 2;
        ListNode* leftList = merge(lists, left, mid);
        ListNode* rightList = merge(lists, mid + 1, right);
        return mergeTwoLists(leftList, rightList);
    }

    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        ListNode* dummy = new ListNode(0);
        ListNode* tail = dummy;
        while (l1 && l2) {
            if (l1->val < l2->val) {
                tail->next = l1;
                l1 = l1->next;
            }
            else {
                tail->next = l2;
                l2 = l2->next;
            }
            tail = tail->next;
        }
        tail->next = (l1) ? l1 : l2;
        return dummy->next;
    }
};

// 辅助函数,用于创建链表
ListNode* createList(std::initializer_list<int> values) {
    ListNode* head = new ListNode(0);
    ListNode* tail = head;
    for (auto value : values) {
        tail->next = new ListNode(value);
        tail = tail->next;
    }
    return head->next;
}

// 辅助函数,用于释放链表内存
void deleteList(ListNode* head) {
    while (head) {
        ListNode* temp = head;
        head = head->next;
        delete temp;
    }
}

// 测试函数
void printList(ListNode* head) {
    while (head) {
        std::cout << head->val << " -> ";
        head = head->next;
    }
    std::cout << "null" << std::endl;
}

int main() {
    // 测试用例
    ListNode* l1 = createList({ 1, 4, 5 });
    ListNode* l2 = createList({ 1, 3, 4 });
    ListNode* l3 = createList({ 2, 6 });

    std::vector<ListNode*> lists = { l1, l2, l3 };
    Solution solution;
    ListNode* mergedList = solution.mergeKLists(lists);

    printList(mergedList);

    // 释放合并后链表的内存
    deleteList(mergedList);

    return 0;
}
  • 输出结果:

1 -> 1 -> 2 -> 3 -> 4 -> 4 -> 5 -> 6 -> null

分治法

分治法(Divide and Conquer)是一种算法设计范式,它通过将问题分解为更小的子问题来解决,然后递归地解决这些子问题,并将结果合并以形成原问题的解。这种方法通常用于可以自然地分解成相似子问题的问题,如排序算法(例如快速排序和归并排序)、矩阵乘法、乘法算法等。

分治法的关键步骤通常包括:

  1. 分解(Divide):将原问题分解为若干个规模较小的相同问题。
  2. 解决(Conquer):独立地解决这些子问题。如果子问题足够小,则可以直接求解。
  3. 合并(Combine):将子问题的解合并以形成原问题的解。

示例:归并排序

归并排序是分治法的一个经典例子。以下是使用分治法实现归并排序的步骤:

  1. 分解:将数组从中间分成两部分。
  2. 解决:递归地对这两部分进行归并排序。
  3. 合并:将排序好的两部分合并成一个有序数组。

下面是归并排序的 C++ 实现:

cpp 复制代码
#include <iostream>
#include <vector>

void merge(std::vector<int>& arr, int l, int m, int r) {
    std::vector<int> temp(r - l + 1);
    int i = l, j = m + 1, k = 0;

    // 合并过程
    while (i <= m && j <= r) {
        if (arr[i] <= arr[j]) {
            temp[k++] = arr[i++];
        } else {
            temp[k++] = arr[j++];
        }
    }

    // 复制左边剩余元素
    while (i <= m) {
        temp[k++] = arr[i++];
    }
    // 复制右边剩余元素
    while (j <= r) {
        temp[k++] = arr[j++];
    }

    // 将临时数组的内容复制回原数组
    for (k = 0; k < temp.size(); k++) {
        arr[l + k] = temp[k];
    }
}

void mergeSort(std::vector<int>& arr, int l, int r) {
    if (l < r) {
        int m = l + (r - l) / 2;
        mergeSort(arr, l, m); // 递归分解左边
        mergeSort(arr, m + 1, r); // 递归分解右边
        merge(arr, l, m, r); // 合并
    }
}

int main() {
    std::vector<int> arr = {38, 27, 43, 3, 9, 82, 10};
    int n = arr.size();

    mergeSort(arr, 0, n - 1);

    std::cout << "Sorted array:\n";
    for (int i : arr) {
        std::cout << i << " ";
    }
    std::cout << std::endl;

    return 0;
}

在这个例子中,mergeSort 函数是一个递归函数,它将数组分成更小的部分,直到每部分只有一个元素(这是最小的有序数组)。然后,merge 函数将这些有序数组合并成更大的有序数组,直到最终得到完全排序的数组。

分治法是解决许多算法问题的强大工具,它强调了递归和问题分解的重要性。

相关推荐
Jerry2 小时前
LeetCode 28. 找出字符串中第一个匹配项的下标
算法
Jerry4 小时前
LeetCode 459. 重复的子字符串
算法
哥不想学算法6 小时前
【C++】字符串字面量拼接
开发语言·c++
海石6 小时前
1500分的题目,确实有实力,不过还是我略胜一筹
算法·leetcode
海石6 小时前
【记忆化搜索】条条大路通AC,走好适合你的那一条,走到后再考虑走得快
算法·leetcode
Jerry8 小时前
LeetCode 151. 反转字符串中的单词
算法
gugucoding9 小时前
31. 【C语言】堆栈与队列的实现
c语言·开发语言·数据结构·链表
ChaoZiLL10 小时前
我的数据结构3——链表(link list)
数据结构·链表
a11177611 小时前
LM 算法迭代过程动画演示(SLAM)
算法
头茬韭菜11 小时前
Context 的生死抉择:四层压缩、截断算法与 Session Memory
算法·ai