LeetCode - 2. 两数相加

目录

题目

思路

时间和空间复杂度

过程

正确的写法


题目

2. 两数相加 - 力扣(LeetCode)

思路

  • 同时遍历两个链表,对应位置的数字相加
  • 处理可能产生的进位
  • 将每一位的计算结果存入新链表

算法步骤

创建虚拟头节点,简化链表操作

维护一个进位变量carry,初始为0

同时遍历两个链表,只要有一个链表还有节点或者有进位,就继续计算

对于每一位的计算:

  • 取两个链表当前位置的值(如果链表已结束则为0)
  • 加上上一位的进位
  • 计算当前位的结果(和对10取模)和新的进位(和整除10)
  • 将当前位的结果添加到结果链表

返回结果链表(跳过虚拟头节点)

4. 关键点

  • 逆序存储的优势:正好符合从低位到高位的计算顺序
  • 虚拟头节点:简化链表操作,不需要特殊处理头节点
  • 统一循环条件:while(l1 || l2 || carry)确保处理所有情况
  • 安全访问:检查指针是否为空,避免空指针异常
  • 进位处理:每一位计算完后,将进位传递到下一位

时间和空间复杂度

  • 时间复杂度:O(max(m,n)),其中m和n是两个链表的长度
  • 空间复杂度:O(max(m,n)),用于存储结果链表

过程

复制代码
l1 = [2,4,3]  // 表示数字342
l2 = [5,6,4]  // 表示数字465

初始状态

复制代码
虚拟头节点: [0] -> null
prev指向虚拟头节点
t = 0
cur1指向l1的头节点: [2] -> [4] -> [3] -> null
cur2指向l2的头节点: [5] -> [6] -> [4] -> null

第一次循环

  • 读取cur1的值: t += 2, t = 2

  • cur1前进: cur1 -> [4] -> [3] -> null

  • 读取cur2的值: t += 5, t = 7

  • cur2前进: cur2 -> [6] -> [4] -> null

  • 创建新节点: [7]

  • 添加到结果链表: [0] -> [7] -> null

  • prev前进: prev -> [7]

  • 计算进位: t = 7 / 10 = 0

    结果链表: [0] -> [7] -> null

    prev
    t = 0
    cur1 -> [4] -> [3] -> null
    cur2 -> [6] -> [4] -> null

第二次循环

  • 读取cur1的值: t += 4, t = 4

  • cur1前进: cur1 -> [3] -> null

  • 读取cur2的值: t += 6, t = 10

  • cur2前进: cur2 -> [4] -> null

  • 创建新节点: [0] (10 % 10 = 0)

  • 添加到结果链表: [0] -> [7] -> [0] -> null

  • prev前进: prev -> [0]

  • 计算进位: t = 10 / 10 = 1

    结果链表: [0] -> [7] -> [0] -> null

    prev
    t = 1 (有进位)
    cur1 -> [3] -> null
    cur2 -> [4] -> null

第三次循环

  • 读取cur1的值: t += 3, t = 4

  • cur1前进: cur1 -> null

  • 读取cur2的值: t += 4, t = 8

  • cur2前进: cur2 -> null

  • 创建新节点: [8]

  • 添加到结果链表: [0] -> [7] -> [0] -> [8] -> null

  • prev前进: prev -> [8]

  • 计算进位: t = 8 / 10 = 0

    结果链表: [0] -> [7] -> [0] -> [8] -> null

    prev
    t = 0
    cur1 -> null
    cur2 -> null

循环结束

  • cur1 = null, cur2 = null, t = 0,不满足继续循环的条件
  • 跳过虚拟头节点,返回结果链表: [7] -> [0] -> [8] -> null

最终结果

复制代码
[7,0,8]  // 表示数字807

读者可能出现的错误写法

cpp 复制代码
class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        ListNode* dummy = new ListNode(0);
        ListNode* newhead = dummy;
        ListNode* cur1 = l1;
        ListNode* cur2 = l2;
        int carry = 0;  // 定义进位变量

        while(cur1 && cur2)
        {
            int nums = 0;
            nums += cur1->val;
            cur1 = cur1->next;

            nums += cur2->val;
            cur2 = cur2->next;

            newhead->next = new ListNode(nums%10);
            if(nums % 10 >1)
            {
                carry += 1;
            } 

        }
    }
};

**循环条件错误:**while(cur1 && cur2)只处理两个链表长度相同的部分,忽略了较长链表的剩余部分和可能的进位

cpp 复制代码
   // 原代码
   while(cur1 && cur2)
   
   // 修改为
   while(cur1 || cur2 || carry)

**原因:**原循环条件只在两个链表都有节点时执行,会忽略较长链表的剩余部分和最后可能存在的进位。修改后的条件确保处理所有情况:任一链表还有节点或存在进位时都会继续计算。

**进位判断错误:**if(nums % 10 > 1)不是正确的进位计算方式,应该是carry = nums / 10

**没有处理进位传递:**计算出的进位没有加到下一位的计算中

cpp 复制代码
   // 原代码
   int nums = 0;
   
   // 修改为
   int nums = carry;

**没有移动结果链表指针:**每次添加新节点后,newhead没有向前移动

cpp 复制代码
   // 原代码
   newhead->next = new ListNode(nums%10);
   
   // 修改为
   newhead->next = new ListNode(nums%10);
   newhead = newhead->next;

原因: 原代码没有移动结果链表的指针,导致每次新节点都会覆盖前一个节点,最终结果链表只有一个节点。修改后确保每次添加新节点后,指针移动到最新节点,保持链表的正确结构。

**没有处理链表长度不同的情况:**当一个链表比另一个长时,需要继续处理较长链表

cpp 复制代码
   // 原代码
   nums += cur1->val;
   cur1 = cur1->next;
   nums += cur2->val;
   cur2 = cur2->next;
   
   // 修改为
   if(cur1) {
       nums += cur1->val;
       cur1 = cur1->next;
   }
   if(cur2) {
       nums += cur2->val;
       cur2 = cur2->next;
   }

**原因:**原代码在链表长度不同时会导致空指针访问。修改后添加了空指针检查,只有当指针不为空时才访问其值,避免程序崩溃。

**没有返回值:**函数末尾缺少返回语句

正确的写法

cpp 复制代码
class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        ListNode* dummy = new ListNode(0);  
        ListNode* newnode = dummy;

        ListNode* cur1 = l1;
        ListNode* cur2 = l2;

        int carry = 0;

        while(cur1 || cur2 || carry)
        {

            int nums = carry;
            if(cur1)
            {
                nums += cur1->val;
                cur1 = cur1->next;
            }

            if(cur2)
            {
                nums += cur2->val;
                cur2 = cur2->next;
            }

            newnode->next = new ListNode(nums%10);
            newnode = newnode->next;

            carry = nums/10;
        }

        ListNode* newhead = dummy->next;
        delete dummy;
        return newhead;
    }
};
相关推荐
江城开朗的豌豆14 分钟前
AJAX请求的'闪电侠'与'树懒'模式:同步VS异步全解析
前端·javascript·面试
早起鸟儿19 分钟前
docker-自动启动java 包
java·linux·运维·docker
风象南26 分钟前
Spring Boot Fat Jar 的三种打包方式
java·spring boot·后端
爱分享的程序员28 分钟前
Nodejs特训专栏-基础篇:2. JavaScript核心知识在Node.js中的应用
前端·javascript·node.js
liulilittle29 分钟前
通过高级处理器硬件指令集AES-NI实现AES-256-CFB算法。
linux·服务器·c++·算法·安全·加密·openssl
移动开发者1号30 分钟前
Android工程中FTP加密传输与非加密传输的深度解析
android·java·kotlin
W说编程33 分钟前
算法导论第一章:算法基础与排序艺术
c语言·数据结构·算法
爱分享的程序员33 分钟前
Nodejs特训专栏-基础篇:1. Node.js环境搭建与项目初始化详细指南
前端·node.js
titan TV man33 分钟前
上海市计算机学会竞赛平台2022年5月月赛丙组最远城市距离
数据结构·算法
军军君011 小时前
基于Springboot+UniApp+Ai实现模拟面试小工具一:系统需求分析及设计
前端·vue.js·人工智能·spring boot·后端·uni-app·node.js