《程序员面试金典(第6版)》面试题 02.05. 链表求和(构建一个新链表)

文章介绍了如何对两个链表表示的反向存放数位的整数进行求和,提供了两种解决方案,一种是创建新链表,另一种是优化后的单个循环方法。还探讨了数位正向存放时的解题思路,利用栈或链表反转。文章强调了链表操作、数据结构选择和大数运算是解题关键,并提及递归与迭代思想的应用。

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

题目解析

给定两个用链表表示的整数,每个节点包含一个数位。这些数位是反向存放的,也就是个位排在链表首部。编写函数对这两个整数求和,并用链表形式返回结果。
题目传送门:面试题 02.05. 链表求和

示例:

输入:(7 -> 1 -> 6) + (5 -> 9 -> 2),即617 + 295
输出:2 -> 1 -> 9,即912

示例:

输入:(6 -> 1 -> 7) + (2 -> 9 -> 5),即617 + 295
输出:9 -> 1 -> 2,即912

进阶:

  • 思考一下,假设这些数位是正向存放的,又该如何解决呢?

解题思路与代码

这道题不算一道太难的链表题,主要还是考验了一下你如何构建一条新链表的操作。基本的逻辑也比较简单。你需要去保存一个进位的变量,你需要满十进一。之后就是创建链表的操作啦。

当然你也可以选择去在原链表上去进行操作。但是我觉得那样子指针指的可能会比较乱。我还是选择创建一条新的链表吧。

方案一:遇十进一,创建新链表

就像刚刚说的那样,我们来实现我们的代码:

具体的代码如下:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        if(l1 == nullptr) return l2;
        if(l2 == nullptr) return l1;
        int carry = 0;

        ListNode * newList = new ListNode(0);
        ListNode * newHead = newList;

        while(l1 != nullptr && l2 != nullptr){
            int sum = l1->val + l2->val + carry;
            ListNode * temp = new ListNode(sum % 10);
            carry = sum / 10;
            l1 = l1->next;
            l2 = l2->next;
            newHead->next = temp;
            newHead = temp;
        }

        while(l1 != nullptr){
            int sum = carry + l1->val;
            ListNode * temp = new ListNode(sum % 10);
            carry = sum / 10;
            newHead->next = temp;
            newHead = temp;
            l1 = l1->next;
        }

        while(l2 != nullptr){
            int sum = carry + l2->val;
            ListNode * temp = new ListNode(sum % 10);
            carry = sum / 10;
            newHead->next = temp;
            newHead = temp;
            l2 = l2->next;
        }

        if(carry > 0){
            ListNode * temp = new ListNode(carry);
            newHead->next = temp;
        }

        return newList->next;
    }
};

在这里插入图片描述

复杂度分析:

时间复杂度:

  • 我们首先遍历了两个链表,这需要的时间是O(n + m),其中n和m分别代表了两个链表的长度。然后你又分别处理了l1和l2剩余的部分,所以总的时间复杂度为O(n + m),这是线性的时间复杂度。

空间复杂度:

  • 你创建了一个新的链表来存储结果,这需要的空间是O(max(n, m)),因为在最坏的情况下,新链表的长度是最长的输入链表的长度。所以这个代码的空间复杂度是O(max(n, m))。

方案二:优化!使代码看起来更加整洁

根据刚刚写的代码,我发现我不需要那么多的while循环,我只需要一个while循环就行了,整体优化了一下代码结构,使代码看起来更简洁,也更简单。

具体的代码如下:

class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        ListNode * head = nullptr;
        ListNode * curr = nullptr;
        int carry = 0;
        while(l1 || l2){
            int v1 = l1 ? l1->val : 0;
            int v2 = l2 ? l2->val : 0;
            int sum = v1 + v2 + carry;
            if(!head) head = curr = new ListNode(sum % 10);
            else{
                curr->next = new ListNode(sum % 10);
                curr = curr->next;
            }
            carry = sum / 10;
            if(l1) l1 = l1->next;
            if(l2) l2 = l2->next;
        }
        if(carry) curr->next = new ListNode(carry);
        return head;
    }
};

在这里插入图片描述

复杂度分析

因为只是优化了一下代码结构,所以时间复杂度与空间复杂度完全没有变化。

进阶:思考一下,假设这些数位是正向存放的,又该如何解决呢?

  • 先开个玩笑,其实可以先翻转链表,然后再进行同为相加操作,hh。这种写法我就不写了,没什么必要。

  • 那换一种思路想想,有没有什么数据结构,可以让我们实现先加链表的最后面的节点呢?
    答案是:栈(stack) 。我们可以分别把链表l1,l2的节点分别压入栈中。此时取出来的元素必定是相同位数。如果有的栈先取完了,和刚刚一样,当做0就行。

  • 注意,虽然说,这里的进阶是让你去思考,如果你拿我的代码去leetcode上去提交,题解肯定是不通过的,这是因为,leetcode里面的案例都是个位数是排在前面的,而我们这个进阶思考是让你将最高位排在最前面。

  • 而将最高位排在最前面其实就运用到了一个技巧,就是用循环来插入头节点。也就是反转链表

具体的代码如下:

class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        stack<int> s1, s2;
        while (l1) {
            s1.push(l1 -> val);
            l1 = l1 -> next;
        }
        while (l2) {
            s2.push(l2 -> val);
            l2 = l2 -> next;
        }
        int carry = 0;
        ListNode* head = nullptr;
        while (!s1.empty() or !s2.empty() or carry != 0) {
            int a = s1.empty() ? 0 : s1.top();
            int b = s2.empty() ? 0 : s2.top();
            if (!s1.empty()) s1.pop();
            if (!s2.empty()) s2.pop();
            int cur = a + b + carry;
            carry = cur / 10;
            cur %= 10;
            auto pos = new ListNode(cur);
            pos -> next = head;
            head = pos;
        }
        return head;
    }
};

总结

这道题的意义在于以下几点:

  • 链表操作:这道题目考察了对链表的基本操作,包括创建新节点,遍历链表,以及处理链表节点之间的链接关系。

  • 数据结构的选择:在不同的数据表示下,相同的问题可能需要不同的解决策略。例如,这个问题中,数位是反向存放的,这样我们可以直接从链表的头部开始操作,方便处理进位问题。但在进阶问题中,数位是正向存放的,我们就需要将链表反转或者使用栈等数据结构,才能便捷地处理进位问题。

  • 大数运算:这道题目也间接涉及到了大数运算的问题。在实际的编程中,我们可能会遇到超出基本数据类型表示范围的大数,这时候就需要用链表、数组或者字符串等方式来表示这些大数,然后实现基本的数学运算,例如这个问题中的加法运算。

  • 递归与迭代思想:在解决这个问题的时候,我们可以用迭代的方式,也可以用递归的方式。这都考察了我们对这两种基本编程思想的理解。

总的来说,这道题目在帮助我们锻炼基本的链表操作技巧的同时,也提高了我们对数据结构选择,大数运算,以及递归与迭代思想的理解。

最后的最后,如果你觉得我的这篇文章写的不错的话,请给我一个赞与收藏,关注我,我会继续给大家带来更多更优质的干货内容

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

德亦周

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值