LeetCode 21. 合并两个有序链表

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

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

示例 2:
输入:l1 = [], l2 = []
输出:[]

示例 3:
输入:l1 = [], l2 = [0]
输出:[0]

提示:
两个链表的节点数目范围是 [0, 50]
-100 <= Node.val <= 100
l1 和 l2 均按 非递减顺序 排列

本文采用思维的递进和代码的逐步优化来解析这道题


本题虽然不复杂,但是很多题目的基础,解决思路与数组一样,一般有两种。一种是新建一个链表,然后分别遍历两个链表,每次都选最小的结点接到新链表上,最后排完。另外一个就是将一个链表结点拆下来,逐个合并到另外一个对应位置上去。这个过程本身就是链表插入和删除操作的拓展,难度不算大,这时候代码是否优美就比较重要了。先看下面这种:

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
    	// 建立新的链表头结点
        ListNode* newHead = (ListNode*)malloc(sizeof(ListNode));
        newHead->val = -1;
        newHead->next = NULL;
        // ans用于记录返回值
        ListNode* ans = newHead;
        // 同时遍历两个链表,类似双指针
        while(list1 != NULL || list2 != NULL) {
        	// case1.两个链表都不空
            if(list1 != NULL && list2 != NULL) {
            	// 比较两个链表当前节点的值,把小的那个接到newHead后面
                if(list1->val < list2->val) {
                    newHead->next = list1;
                    list1 = list1->next;
                } else if(list1->val > list2->val) {
                    newHead->next = list2;
                    list2 = list2->next;
                } else {
                	// 两个链表当前节点的值相同时,都接到后面去
                    newHead->next = list1;
                    list1 = list1->next;
                    newHead = newHead->next;
                    newHead->next = list2;
                    list2 = list2->next;
                }
                newHead = newHead->next;
            } else if(list1 != NULL && list2 == NULL) {
            	// case2.List2已经遍历完了,把list1剩余元素全部接到后面去
                newHead->next = list1;
                list1 = list1->next;
                newHead = newHead->next;
            } else if(list2 != NULL && list1 == NULL) {
            	// case3.list1同理
                newHead->next = list2;
                list2 = list2->next;
                newHead = newHead->next;
            }
        }
        return ans->next;
    }
};

看到这段代码,你会给他打多少分呢
这段代码明显非常臃肿,因为所有的处理都放在一个大循环里,我们是不是可以把某个或某几个处理拉出来呢,也就是这样:

ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
    ListNode* newHead = (ListNode*)malloc(sizeof(ListNode));
    newHead->val = -1;
    newHead->next = NULL;
    ListNode* ans = newHead;
    while(list1 != NULL && list2 != NULL) {
        if(list1->val < list2->val) {
            newHead->next = list1;
            list1 = list1->next;
        } else if(list1->val > list2->val) {
            newHead->next = list2;
            list2 = list2->next;
        } else {
            newHead->next = list1;
            list1 = list1->next;
            newHead = newHead->next;
            newHead->next = list2;
            list2 = list2->next;
        }
        newHead = newHead->next;
    }
    while(list1 != NULL) {
        newHead->next = list1;
        list1 = list1->next;
        newHead = newHead->next;
    } 
    while (list2 != NULL) {
        newHead->next = list2;
        list2 = list2->next;
        newHead = newHead->next;
    }
    return ans->next;
}

肉眼可见的瘦身哈,其实只不过是把上面的大while里面处理的三种case分成三个while进行处理,代码是变好看了,可是耗时更多了,我们来进一步优化:

我们看后面两个小的while循环,都是把一个链表拼到另一个链表的后面,而链表拼接只需要把头结点拼好,后面的是不是自然而然就跟着接上去了啊,因此这两个循环可以直接简化到一行代码:

ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
    ListNode* newHead = (ListNode*)malloc(sizeof(ListNode));
    newHead->val = -1;
    newHead->next = NULL;
    ListNode* ans = newHead;
    while(list1 != NULL && list2 != NULL) {
        if(list1->val < list2->val) {
            newHead->next = list1;
            list1 = list1->next;
        } else if(list1->val > list2->val) {
            newHead->next = list2;
            list2 = list2->next;
        } else {
            newHead->next = list1;
            list1 = list1->next;
            newHead = newHead->next;
            newHead->next = list2;
            list2 = list2->next;
        }
        newHead = newHead->next;
    }
    newHead->next = list1 == NULL ? list2 : list1;
    return ans->next;
}

用的是三目运算符,尾巴是不是非常简洁了,但是这样一对比显得肚子部分还是很臃肿啊,那么大一个while,我们接着优化:
我们重新审视一下while循环里面的逻辑,上面的想法是分三种情况:<, >, ==。考虑一下把相等的这种情况跟前面合并起来,于是就可以这样写:

ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
    ListNode* newHead = (ListNode*)malloc(sizeof(ListNode));
    ListNode* ans = newHead;
    while(list1 != NULL && list2 != NULL) {
        if(list1->val <= list2->val) {
            newHead->next = list1;
            list1 = list1->next;
        } else {
            newHead->next = list2;
            list2 = list2->next;
        } 
        newHead = newHead->next;
    }
    newHead->next = list1 == NULL ? list2 : list1;
    return ans->next;
}

这种方式明显更高级,但是考虑的方面也很多,一开始想确实不一定就能想到这一步,但是通过思维递进和代码逐步优化,一样能写出优雅的代码

合并K个链表

会了合并两个链表,那合并K个链表就迎刃而解了,直接亮亮合并到最后,直接上代码:

struct ListNode* mergeKLists(struct ListNode* lists[], int size) {
	struct ListNode* res = NULL;
    for (int i = 0; i < size; i++) {
        res = mergeTwoLists(res, lists[i]);
    }      
    return res; 
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值