两个有序单链表的合并&两个有序数组的合并

本文介绍两种常见的有序列表合并方法:一是合并两个有序单链表,二是将两个有序数组合并到同一个数组中,假设该数组有足够的空间。对于链表合并采用递归方式;而对于数组,则是从后向前比较并合并。

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

1.两个有序单链表的合并

typedef struct ListNode{
	int val;
	ListNode* next;
}ListNode;

ListNode* mergeList(ListNode* pHead1,ListNode* pHead2){
	if (pHead1==NULL)	return pHead2;
	if (pHead2 == NULL)	return pHead1;
	ListNode* pMergeHead = NULL;
	if (pHead1->val < pHead2->val){
		pMergeHead = pHead1;
		pMergeHead->next = mergeList(pHead1->next,pHead2);
	}
	else{
		pMergeHead = pHead2;
		pMergeHead->next = mergeList(pHead2->next,pHead1);
	}
	return pMergeHead;
}

2,.两个有序数组的合并

从后往前合并,假设第一个数组足够大,可以容纳合并后的数组。

#define MAXN 1024
void combine(int *a, int *b, int len1, int len2){
	if (a == NULL || b == NULL || (len1 + len2) > MAXN)
		return;
	int new_point;
	int a_point = len1 - 1;
	int b_point = len2 - 1;
	new_point = len1 + len2 - 1;//总的长度
	while (a_point>=0&&b_point>=0){
		if (a[a_point] > b[b_point]){
			a[new_point--] = a[a_point--];
		}
		else{
			a[new_point--] = b[b_point--];
		}
	}
	while (a_point >= 0){
		a[new_point--] = a[a_point--];
	}
	while (b_point >= 0){
		a[new_point--] = b[b_point--];
	}
	return;
}


&lt;think&gt;嗯,我现在得仔细想想两个有序单链表的归并运算怎么实现。首先,这个问题应该属于数据结构中的链表操作部分,归并的话应该类似于归并排序中的合并步骤。不过具体到单链表,可能需要注意指针的操作,因为链表不能像数组那样随机访问。 那问题来了,单链表归并的大体思路应该是什么样的呢?假设有两个已经按升序排列的单链表,需要把它们合并成一个新的有序链表。可能需要创建一个新的链表头,然后逐个比较两个链表的节点,把较小的节点接在新链表的后面,直到其中一个链表为空,然后把剩下的部分接上。 不过具体怎么操作呢?比如说,是否需要使用虚拟头节点(dummy node)来简化操作?因为在处理链表的时候,处理头节点通常会比较麻烦,尤其是当初始时新链表为空的时候。使用虚拟头节点的话,可以统一处理所有节点,最后返回dummy.next就行了。 然后,具体的步骤应该是这样的:初始化一个dummy节点和一个当前指针current指向它。然后循环比较两个链表当前节点的值,把较小的那个节点接到current后面,并移动对应的链表指针和current指针。直到其中一个链表遍历完,然后把剩下的那个链表直接接上。 不过需要注意,这里需要处理的是单链表,所以每次只能改变指针的next指向,而无法回溯。比如,当比较两个链表的当前节点时,假设链表1的节点值更小,那么current.next应该指向这个节点,然后链表1的指针向后移动一位,current也移动到current.next的位置。重复这个过程。 举个例子,比如链表1是1-&gt;3-&gt;5,链表2是2-&gt;4-&gt;6。初始时dummy节点后面为空,current指向dummy。比较1和2,1更小,所以current.next指向1,current移动到1,链表1的指针移到3。接着比较3和2,这时候2更小,current.next指向2,current移动到2,链表2指针移到4。依此类推,直到某个链表遍历完。 这时候剩下的链表可能还有节点,比如其中一个链表更长的情况。这时候只需要把current.next指向剩下的那个链表的当前节点即可,因为剩下的部分已经是有序的,不需要再逐个比较。 然后考虑特殊情况,比如其中一个链表为空的情况。比如链表1为空,直接返回链表2;链表2为空,返回链表1。所以在代码开始时应该先处理这两种情况。 此外,需要确保在操作过程中不要出现空指针异常。比如,当某一个链表已经遍历到末尾的时候,不能再访问其val值。所以循环的条件应该是两个链表的当前节点都不为空的时候进行比较,一旦其中一个为空就退出循环,处理剩下的部分。 那具体实现的步骤总结下来应该是: 1. 处理特殊情况:若其中一个链表为空,直接返回另一个链表。 2. 创建虚拟头节点dummy和current指针指向它。 3. 循环比较两个链表的当前节点,将较小的节点接在current后面,并移动相应指针。 4. 当循环结束后,将current的next指向剩下的非空链表。 5. 返回dummy.next作为合并后的链表头。 不过这里可能需要考虑是否在原链表上进行操作还是创建新节点。一般来说,归并操作可能不需要创建新节点,而是直接调整指针指向。这样可以节省空间,达到O(1)的空间复杂度(如果不算虚拟节点的话,虚拟节点是O(1)的)。不过如果题目要求不能修改原链表,可能需要创建新节点,但通常情况下,合并两个链表应该允许修改指针。 那时间复杂度是多少呢?因为每个节点都会被访问一次,所以时间复杂度是O(n+m),其中n和m是两个链表的长度。空间复杂度的话,除了几个指针变量外,虚拟节点是O(1),所以总的空间复杂度是O(1)。 那如何测试这个算法呢?比如测试两个链表有部分重叠的情况,或者其中一个链表完全大于另一个的情况。比如链表1是1-&gt;2-&gt;4,链表2是1-&gt;3-&gt;4。合并后的结果应该是1-&gt;1-&gt;2-&gt;3-&gt;4-&gt;4。这时候算法应该正确处理重复的值,保持有序。 另外,如果其中一个链表比另一个长很多,比如链表1只有1个节点,链表2有很多节点,这时候循环处理完链表1之后,直接将current的next指向链表2剩下的部分即可。 那代码的大致结构可能是这样的(假设是Java或者类似的伪代码): public ListNode mergeTwoLists(ListNode l1, ListNode l2) { if (l1 == null) return l2; if (l2 == null) return l1; ListNode dummy = new ListNode(0); ListNode current = dummy; while (l1 != null &amp;&amp; l2 != null) { if (l1.val &lt; l2.val) { current.next = l1; l1 = l1.next; } else { current.next = l2; l2 = l2.next; } current = current.next; } current.next = (l1 != null) ? l1 : l2; return dummy.next; } 这样看起来应该没问题。不过需要注意,在移动l1或者l2指针时,必须先将current.next指向当前节点,然后再移动原链表的指针,否则会导致指针错误。 有没有可能出错的地方呢?比如在处理剩下的链表部分时,是否有可能出现循环引用?应该不会,因为剩下的链表本身已经是null结尾的,所以直接链接即可。 总结下来,归并两个有序单链表的关键是使用双指针比较节点,并按顺序链接,同时使用虚拟头节点简化操作。这样就能高效地合并两个链表,且不需要额外空间。&lt;/think&gt;### 两个有序单链表的归并运算详解 #### 问题分析 归并两个**有序单链表**的目标是将它们合并为一个新的有序单链表。要求时间复杂度为 $O(n+m)$($n$ 和 $m$ 为两链表长度),空间复杂度为 $O(1)$(仅需调整指针,不创建新节点)。 --- #### 核心思路 1. **虚拟头节点**:通过创建虚拟头节点(dummy node)简化边界条件处理,避免对空链表的特殊判断。 2. **双指针遍历**:用两个指针分别遍历两个链表,比较节点值并按序链接到新链表中。 3. **处理剩余节点**:当某一链表遍历完成后,将另一链表的剩余部分直接链接到新链表末尾。 --- #### 具体步骤 1. **处理特殊情况**: - 若链表1为空,直接返回链表2。 - 若链表2为空,直接返回链表1。 2. **初始化虚拟头节点**: ```java ListNode dummy = new ListNode(0); // 虚拟头节点 ListNode current = dummy; // 当前指针 ``` 3. **双指针遍历与比较**: - 循环条件:两链表均未遍历完。 - 比较两链表当前节点的值,将较小的节点链接到 `current` 后,并移动对应指针。 ```java while (l1 != null &amp;&amp; l2 != null) { if (l1.val &lt; l2.val) { current.next = l1; // 链接较小节点 l1 = l1.next; // 移动链表1指针 } else { current.next = l2; // 链接较小节点 l2 = l2.next; // 移动链表2指针 } current = current.next; // 移动新链表指针 } ``` 4. **链接剩余节点**: - 将未遍历完的链表直接链接到新链表末尾。 ```java current.next = (l1 != null) ? l1 : l2; ``` 5. **返回结果**: ```java return dummy.next; // 虚拟头节点的下一个节点为实际头节点 ``` --- #### 示例演示 **输入**: - 链表1:$1 \rightarrow 3 \rightarrow 5$ - 链表2:$2 \rightarrow 4 \rightarrow 6$ **执行过程**: 1. 初始化 `dummy` 和 `current`。 2. 比较 `1` 和 `2`,链接 `1`,移动链表1指针。 3. 比较 `3` 和 `2`,链接 `2`,移动链表2指针。 4. 重复上述步骤,直到某一链表遍历完。 5. 将剩余节点 `5` 或 `6` 链接到末尾。 **输出**: $1 \rightarrow 2 \rightarrow 3 \rightarrow 4 \rightarrow 5 \rightarrow 6$ --- #### 复杂度分析 - **时间复杂度**:$O(n + m)$,每个节点仅访问一次。 - **空间复杂度**:$O(1)$,仅使用常数级别的额外空间。 --- #### 边界条件测试 1. **一个链表为空**:直接返回另一链表。 2. **所有节点值相同**:如链表1为 $2 \rightarrow 2$,链表2为 $2 \rightarrow 2$,合并后仍有序。 3. **链表长度差异大**:如链表1仅1个节点,链表2有100个节点。 --- #### 代码实现(Java) ```java public ListNode mergeTwoLists(ListNode l1, ListNode l2) { if (l1 == null) return l2; if (l2 == null) return l1; ListNode dummy = new ListNode(0); ListNode current = dummy; while (l1 != null &amp;&amp; l2 != null) { if (l1.val &lt; l2.val) { current.next = l1; l1 = l1.next; } else { current.next = l2; l2 = l2.next; } current = current.next; } current.next = (l1 != null) ? l1 : l2; return dummy.next; } ``` --- #### 关键点总结 - **虚拟头节点**:避免处理头节点的复杂逻辑。 - **指针操作顺序**:先链接节点,再移动指针。 - **剩余节点处理**:直接链接,无需逐个比较。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值