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


    题目解析

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

    示例:

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

    示例:

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

    进阶:

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

    解题思路与代码

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

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

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

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

    具体的代码如下:

    /**
     * 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;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    在这里插入图片描述

    复杂度分析:

    时间复杂度:

    • 我们首先遍历了两个链表,这需要的时间是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;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    在这里插入图片描述

    复杂度分析

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

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

    • 先开个玩笑,其实可以先翻转链表,然后再进行同为相加操作,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;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    总结

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

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

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

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

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

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

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

  • 相关阅读:
    Rust 安装与版本更新
    19.vue渲染系统的实现
    blazor调试部署全过程
    小米手机抓取hci log
    跟着李老师学线代——矩阵(持续更新)
    自媒体写作,怎么写出让人看了就想点击的标题呢?
    pyqt5学习-01 UI界面创建以及生成python代码
    Android-Framework 三方应用默认权限都不弹窗
    ATTransUNet:一种增强型混合Transformer结构用于超声图像分割
    洛谷 P1160 队列安排
  • 原文地址:https://blog.csdn.net/weixin_49503250/article/details/130855089