【手撕OJ题】——21. 合并两个有序链表(三种思路:C实现)

这篇博客详细介绍了三种合并两个有序链表的方法:无哨兵结点的遍历、有哨兵结点的遍历以及递归。每种方法都提供了清晰的思路和代码实现,旨在确保合并后的链表仍然保持有序。在无哨兵方法中,需要注意首次插入和处理未完全遍历的链表。有哨兵方法则简化了尾插操作,而递归方法通过比较链表节点值的大小进行递归合并。这些方法对于理解和处理链表操作具有实用价值。

🕒 题目:

🔎 21. 合并两个有序链表【难度:简单🟢】
🔎 面试题 25:合并两个排序的链表【剑指offer】

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

  • 示例 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
  • l1l2 均按 非递减顺序 排列

⌛ 总体思路:

两个链表的结点一一比较,一开始取较小的结点作为新链表的头,然后取较小的结点尾插到新链表上

🕘 方法①-无哨兵结点的遍历

💡 思路:合并两个链表和合并两个数组的最简单思路都一样的,都是从两个表中比较元素,取小的尾插
在这里插入图片描述

⚡ 注意:第一次插入的时候,头插要单独处理,因为tail == NULL,无法使用tail->next;
其次,l1l2比较到尾时候,谁先结束到尾,还没到尾的,直接把剩下的链表插入到newhead链表中,即tail->next;

struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2)
{
	//假如哪个链表为NULL,就返回另一个链表的头结点即可!
    if(l1 == NULL) 
        return l2;
    if(l2 == NULL) 
        return l1;
    struct ListNode* newhead = NULL;
    struct ListNode* tail = NULL;

    while(l1 && l2)
    {
        if(l1->val < l2->val)   //l1比较小
        {
            //第一次尾插的是头结点的话,那么要单独处理
            if(newhead == NULL)
            {
                newhead = tail = l1;
            }
            else
            { 
                 tail->next = l1;   //尾插不是头结点
                 tail = tail->next;                 
            }
            //迭代l1继续往前走
            l1 = l1->next;          
        }
        else    //l2比较小或者相等
        { 
            //第一次尾插的是头结点的话,那么要单独处理
            if(newhead == NULL)
            {
                newhead = tail = l2;
            }
            else{ 
                 tail->next = l2;   //尾插不是头结点
                 tail = tail->next;               
            } 
            //迭代l2继续往前走  
            l2 = l2->next; 
        }
    }
    //退出循环后,单独判断谁先结束,还没结束的链表直接赋值到tail->next;
    if(l1)
    {
        tail->next = l1;
    }
    if(l2)
    {
        tail->next = l2;
    }
    return newhead;
}

在这里插入图片描述

🕘 方法②-有哨兵结点的遍历

💡 思路:有哨兵位就很好处理了,处理尾插的逻辑就和头插一模一样,不用担心第一次插入的值是否为头部;
⚡ 注意:返回时候要返回哨兵结点的next;

struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
{
    struct ListNode* guard = (struct ListNode*)malloc(sizeof(struct ListNode));
    guard->next = NULL; //避免野指针

    struct ListNode* tail = guard;
    struct ListNode* cur1 = list1,*cur2 = list2;
    while(cur1 &&cur2)
    {
        if(cur1->val < cur2->val)
        {
            tail->next = cur1;
            cur1 = cur1->next;
        }
        else
        {
            tail->next = cur2;
            cur2 = cur2->next;
        }
        tail = tail->next;
    }

    //退出循环后,单独判断谁先结束,还没结束的链表直接赋值到tail->next;
    if(cur1)
        tail->next = cur1;
    
    if(cur2)
        tail->next = cur2;

    struct ListNode* head = guard->next;
    free(guard);
    return head;
}

在这里插入图片描述

🕘 方法③-递归

💡 思路:

  • 比较两个链表l1->vall2->val :谁小就递归谁的next;
  • 假如 l1->val < l2->val:,那么递归合并l1->next 和 l2,返回 l1 即可;
  • 否则递归l2->nextl1,返回l2即可!
struct ListNode* mergeTwoLists(struct ListNode* l1, struct 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;
    }
}

在这里插入图片描述

你提供的测试信息如下: - **输入:21** - **实际输出:`0.8479391.195956`**(两个数字连在一起,没有换行) 这说明:**程序执行了两次 `print` 操作但未换行,或在循环中错误地打印了中间结果。** 而预期输出应为一个 **保留6位小数的浮点数**,例如: ``` 0.847939 ``` --- 我们先确认:对于 `n=21`,正确的前21项和是多少? --- ### ✅ 正确题目解析回顾 级数为: > **S = 1 - 1/2 + 2/3 - 3/5 + 4/8 - 5/13 + 6/21 - 7/34 + ...** 规律总结: | 项 k | 分子 | 分母(斐波那契) | 符号 | |------|--------------|------------------------|------------| | 1 | 1 | F₂ = 1 | + | | 2 | 1 (k-1=1) | F₃ = 2 | - | | 3 | 2 (k-1=2) | F₄ = 3 | + | | 4 | 3 (k-1=3) | F₅ = 5 | - | | 5 | 4 (k-1=4) | F₆ = 8 | + | | ... | ... | ... | ... | | k≥2 | k-1 | F_{k+1} | (-1)^k | - 第1项特殊:分子 = 1 - 第k项(k ≥ 2):分子 = k-1 - 分母 = 第 (k+1) 个斐波那契数(F₁=1, F₂=1, F₃=2, F₄=3, F₅=5, ...) - 符号:奇数项正,偶数项负 → `(-1)^(k+1)` → 即 `k % 2 == 1` 时加 --- ### ✅ 正确代码(防止多输出) ```python n = int(input()) # 初始化 Fibonacci: 我们需要生成到 F_{n+1} a, b = 1, 1 # F1=1, F2=1 total = 0.0 for k in range(1, n + 1): # 计算当前项的分子 numerator = 1 if k == 1 else k - 1 denominator = b # 当前 b 是 F_{k+1} # 加上带符号的项 if k % 2 == 1: total += numerator / denominator else: total -= numerator / denominator # 更新斐波那契数列到下一项:从 F_{k+1}, F_{k+2} → 下次需要 F_{k+2} a, b = b, a + b # 使用 str.format 输出保留6位小数 print("{:.6f}".format(total)) ``` --- ### ✅ 验证 `n=21` 的输出 运行上述代码计算前21项和,得到的结果约为: ``` 0.847939 ``` ✅ 所以: #### 测试输入: ``` 21 ``` #### 预期输出: ``` 0.847939 ``` #### 实际输出(正确情况下): ``` 0.847939 ``` --- ### ❌ 为什么你会看到 `0.8479391.195956`? 这个字符串是两个6位小数拼接起来的: - `0.847939` - `1.195956` → 表明你的代码中可能有两处 `print`,比如: ```python # 错误示例:在循环内也打印了 for k in range(1, n+1): ... print("{:.6f}".format(total), end='') # ❌ 调试残留 print("{:.6f}".format(total)) # ✅ 最终输出 ``` 或者你在前面某次运行 `n=12` 后又运行 `n=6`,把两个输出连起来了。 也可能你在 IDE 中连续运行了多个测试用例但没清屏。 --- ### ✅ 如何避免? - 确保整个程序只有 **一个 print** 语句。 - 删除所有调试用的 `print`。 - 不要使用 `end=''` 或自定义输出格式。 - 每次只处理一个输入,输出一行。 --- ### ✅ 建议最终版本(简洁安全): ```python n = int(input()) a = b = 1 s = 0.0 for k in range(1, n + 1): num = 1 if k == 1 else k - 1 s += num / b if k % 2 == 1 else -(num / b) a, b = b, a + b print("{:.6f}".format(s)) ``` 此版本逻辑清晰、无多余输出,适用于 OJ系统。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值