关于合并有序链表和数组的问题:

本文详细介绍了如何合并有序数组和链表的方法,包括直接数组合并、链表遍历合并及递归合并,并深入探讨了合并K个排序链表的两种策略:分治法和使用最小堆数据结构,提供了丰富的代码实例。

1.合并两个有序数组

将两个按升序排列的数组仍按升序合并存放到另一个数组中,要求每个
数都一次到位,不得在新数组中重新排序。

#include <stdio.h>
int main(void)
{
    int b[5] = {1, 6, 17, 56, 72};
    int a[5] = {7, 23, 32, 33, 82};
    int c[10], i, j, k, p;
    i = j = k = 0;         //i 对应 a 数组,j 对应 b 数组,k 对应 c 数组;
    while (i < 5 && j < 5) //判断 a,b 两数组是否比较完毕;
    {
        if (a[i] < b[j])
            c[k++] = a[i++];
        else
            c[k++] = b[j++];
    }
    if (i > 4) //把 b 赋给 c
    {
        for (p = j; p < 5; p++)
            c[k++] = b[p];
    }
    if (j > 4) //把 a 赋给 c
    {
        for (p = i; p < 5; p++)
            c[k++] = a[p];
    }
    for (i = 0; i < 10; i++) //输出 c
        printf("%4d", c[i]);
    printf("\n");
}

2.合并两个有序链表

*/
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
//遍历解法
class Solution
{
  public:
    ListNode *mergeTwoLists(ListNode *l1, ListNode *l2)
    {
        ListNode result = ListNode(-100);
        ListNode *test = &result;

        ListNode *temp1 = l1;
        ListNode *temp2 = l2;

        while (temp1 && temp2)
        {
            //注意这里判断了相等的情况
            //temp1->val == temp2->val
            if (temp1->val <= temp2->val)
            {
                test->next = temp1;
                temp1 = temp1->next;
                test = test->next;
            }
            else
            {
                test->next = temp2;
                temp2 = temp2->next;
                test = test->next;
            }
        }
        if (temp1)
            test->next = temp1;
        if (temp2)
            test->next = temp2;
        return result.next;
    }
};
//递归解法
class Solution
{
  public:
    ListNode *mergeTwoLists(ListNode *l1, ListNode *l2)
    {
        if (l1 == NULL)
            return l2;
        if (l2 == NULL)
            return l1;
        //还是判断了相等
        if (l1->val <= l2->val)
        {
            l1->next = mergeTwoLists(l1->next, l2);
            return l1;
        }
        else
        {
            l2->next = mergeTwoLists(l1, l2->next);
            return l2;
        }
    }
};

3.合并K个排序链表

思路1:分治法

简单来说就是不停的对半划分,比如k个链表先划分为合并两个k/2个链表的任务,再不停的往下划分,直到划分成只有一个或两个链表的任务,才开始进行合并。举个例子来说比如合并6个链表,那么按照分治法,我们首先分别合并0和3,1和4,2和5。这样下一次只需合并3个链表,我们再合并1和3,最后和2合并就可以了。

代码中的k是通过 (n+1)/2 计算的,这里为啥要加1呢,这是为了当n为奇数的时候,k能始终从后半段开始,比如当n=5时,那么此时k=3,则0和3合并,1和4合并,最中间的2空出来。当n是偶数的时候,加1也不会有影响,比如当n=4时,此时k=2,那么0和2合并,1和3合并,完美解决问题.

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        int len = lists.size();
        if (len == 0) return NULL;
        while (len > 1) {
            int k = (len + 1) / 2;
            for (int i = 0; i < len / 2; ++i) {
                lists[i] = mergeTwoLists(lists[i], lists[i + k]);
            }
            len = k;
        }
        return lists[0];
    }
private:
    ListNode *mergeTwoLists(ListNode *l1, ListNode *l2)
    {
        if (l1 == NULL)
            return l2;
        if (l2 == NULL)
            return l1;
        if (l1->val <= l2->val)
        {
            l1->next = mergeTwoLists(l1->next, l2);
            return l1;
        }
        else
        {
            l2->next = mergeTwoLists(l1, l2->next);
            return l2;
        }
    }
};

思路2:最小堆的数据结构

我们再来看另一种解法,这种解法利用了最小堆这种数据结构,我们首先把k个链表的首元素都加入最小堆中,它们会自动排好序。然后我们每次取出最小的那个元素加入我们最终结果的链表中,然后把取出元素的下一个元素再加入堆中,下次仍从堆中取出最小的元素做相同的操作,以此类推,直到堆中没有元素了,此时k个链表也合并为了一个链表,返回首节点即可,代码如下:

class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        auto cmp = [](ListNode*& a, ListNode*& b) {
            return a->val > b->val;
        };
        priority_queue<ListNode*, vector<ListNode*>, decltype(cmp) > q(cmp);
        for (auto node : lists) {
            if (node) q.push(node);
        }
        ListNode *dummy = new ListNode(-1), *cur = dummy;
        while (!q.empty()) {
            auto t = q.top(); q.pop();
            cur->next = t;
            cur = cur->next;
            if (cur->next) q.push(cur->next);
        }
        return dummy->next;
    }
};

合并K个排序链表的思路来自:[LeetCode] Merge k Sorted Lists 合并k个有序链表 ,其中还列举许多种解法,可自行查阅.

<think>嗯,用户想学习如何合并两个有序链表或有序数组的算法。我需要先回顾下之前的相关回答引用内容。用户之前的问题是关于如何在数组中查找特定元素,现在转向合并操作,可能是在学习算法的基础知识,或者准备面试题。 首先,我需要确定用户的需求。他们可能希望了解合并有序据结构的常见方法,比如迭代法、递归法,以及针对数组的不同处理方式。根据提供的引用,特别是引用3、4、5,有关于链表数组合并的不同方法,比如暴力解法、双指针、递归等。 用户提到的合并两个有序链表有序数组,可能需要分步骤讲解。对于链表,通常有迭代递归两种方法,而数组可能需要双指针从前往后或者从后往前的方法,特别是处理原地合并的情况,比如LeetCode 88题。 需要检查是否有用户可能存在的误区。例如,合并数组时,如果从前向后合并可能会覆盖元素,所以从后向前更高效。而链表的递归方法虽然简洁,但可能有栈溢出的风险,这时候迭代更安全。用户提供的引用5中提到了数组合并时使用新数组,但实际可能需要优化到O(1)空间,特别是当nums1有足够空间时。 还要注意代码示例的正确性,比如链表的指针操作,数组的索引处理。用户提供的引用中有Java、C++、Python的示例,可能需要统用用户熟悉的语言,但用户之前的问题中有CPython的例子,所以可能需要多语言支持,但用户现在可能更关注算法本身。 需要结构清晰,分链表数组两部分,每种据结构下再分方法,比如直接合并、递归等。同时,比较不同方法的优缺点,时间空间复杂度,适用场景。例如,引用4提到递归在链表较长时可能不安全,迭代更可靠。 可能还需要提到相关题目,如合并K个链表或合并区间,但用户当前问题只涉及两个,所以作为扩展即可。最后,生成相关问题帮助用户巩固知识,比如时间复杂度的比较,优化方法,处理重复元素等。 总结下来,回答需要包括: 1. 合并有序链表的迭代递归方法,代码示例,复杂度分析。 2. 合并有序数组的双指针方法(从后向前),处理原地合并,代码示例。 3. 方法比较适用场景。 4. 相关问题,如优化、复杂度、扩展问题等。</think>### 合并两个有序链表数组的算法详解 #### 合并两个有序链表 有序链表合并是链表操作中的经典问题,核心思路是通过指针逐步比较节点值,构建新链表。以下是两种主流实现方法: --- ##### 1. 迭代法(双指针) 通过两个指针遍历链表,选择较小值的节点插入结果链表,直至完成合并。 **代码实现(Python)**: ```python class ListNode: def __init__(self, val=0, next=None): self.val = val self.next = next def mergeTwoLists(l1: ListNode, l2: ListNode) -> ListNode: dummy = ListNode() # 哨兵节点简化操作 curr = dummy while l1 and l2: if l1.val < l2.val: curr.next = l1 l1 = l1.next else: curr.next = l2 l2 = l2.next curr = curr.next curr.next = l1 if l1 else l2 # 链接剩余节点 return dummy.next ``` **时间复杂度**:$O(n+m)$,空间复杂度:$O(1)$(仅修改指针)[^4]。 --- ##### 2. 递归法 递归法通过比较当前节点值,选择较小节点作为当前节点,并递归合并剩余部分。 **代码实现(Java)**: ```java public ListNode mergeTwoLists(ListNode list1, ListNode list2) { if (list1 == null) return list2; if (list2 == null) return list1; if (list1.val < list2.val) { list1.next = mergeTwoLists(list1.next, list2); return list1; } else { list2.next = mergeTwoLists(list1, list2.next); return list2; } } ``` **时间复杂度**:$O(n+m)$,空间复杂度:$O(n+m)$(递归栈)[^4]。 --- #### 二、合并两个有序数组 数组合并的核心是双指针技巧,通常需从后向前填充以避免覆盖原据。 ##### 双指针法(从后向前) **适用场景**:数组1有足够空间容纳合并后的所有元素(如LeetCode 88题)。 **代码实现(Python)**: ```python def merge(nums1, m, nums2, n): p1, p2, p = m-1, n-1, m+n-1 # 指针从后向前 while p1 >= 0 and p2 >= 0: if nums1[p1] > nums2[p2]: nums1[p] = nums1[p1] p1 -= 1 else: nums1[p] = nums2[p2] p2 -= 1 p -= 1 nums1[:p2+1] = nums2[:p2+1] # 处理剩余元素 ``` **时间复杂度**:$O(n+m)$,空间复杂度:$O(1)$[^5]。 --- #### 三、方法对比与选择 | 据结构 | 方法 | 优点 | 缺点 | |----------|------------|-----------------------|-----------------------| | 链表 | 迭代法 | 空间效率高,栈安全 | 代码稍长 | | 链表 | 递归法 | 代码简洁 | 栈溢出风险(长链表) | | 数组 | 双指针法 | 原地操作,无需额外空间| 需数组预留足够空间 | --- #### 四、扩展应用 1. **合并K个有序链表**:使用优先队列(最小堆)优化时间复杂度至$O(N \log K)$[^2]。 2. **合并有序区间**:需先排序后合并重叠区间(如LeetCode 56题)。 --- ### 相关问题 1. **迭代法递归法在合并链表时的时间复杂度是否相同?为什么递归法的空间复杂度更高?** 2. **如果合并两个数组时不允许使用额外空间,如何优化算法?** 3. **合并K个有序链表时,如何利用分治思想降低时间复杂度?** 4. **在处理链表的递归合并时,如何避免栈溢出问题?** 5. **为什么合并数组时从后向前遍历比从前向后更高效?**
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值