445. 两数相加 II + (Deque与Stack源码解析)

445. 两数相加 II + (Deque与Stack源码解析)

https://leetcode.cn/problems/add-two-numbers-ii/

题目

给你两个 非空 链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。

你可以假设除了数字 0 之外,这两个数字都不会以零开头。

提示:

  • 链表的长度范围为 [1, 100]
  • 0 <= node.val <= 9
  • 输入数据保证链表代表的数字无前导 0

进阶:如果输入链表不能翻转该如何解决?

我的思考与题解

这题两数相加,是进阶版的2.两数相加,之前做过,之前是低位位于链表首,现在是高位位于链表说,理论上来说我只要反转链表再像2.两数相加递归求出值即可,那这道理就没意义了,我们考虑下如何在不反转输入链表仍可达到效果。

问题1:因为高位在前我们无法再同时取出链表来对应低位,如何达到低位相加再进阶到高位呢?

​ 在同时递归到l1.next==null && l2.next==null时表示都递归到了末尾,然后开始相加返回。

问题2:递归时在堆栈上一层应该保存双方的父级,而且如果l1长于l2那么当l1遍历完成,l2的上级父级元素甚至上上级父级元素都无法查找,如何保存呢?

​ 可以用stack栈来保存然后先进后弹出,那其实没必要走递归,我同时循环两个入参不就可以了?试试

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        Stack<Integer> stack1 = new Stack<>();
        Stack<Integer> stack2 = new Stack<>();

        // 循环将l1,l2加入栈中
        while (l1 != null || l2 != null) {
            if (l1 != null) {
                stack1.push(l1.val);
                l1 = l1.next;
            }
            if (l2 != null) {
                stack2.push(l2.val);
                l2 = l2.next;
            }
        }

        int nextAdd = 0;
        ListNode back = null;
        while (!stack1.empty() || !stack2.empty() || nextAdd !=0) {
            ListNode temp = back;
            Integer i1 = stack1.empty() ? 0 : stack1.pop();
            Integer i2 = stack2.empty() ? 0 : stack2.pop();
            int val = (i1 + i2 + nextAdd) % 10;
            nextAdd = (i1 + i2 + nextAdd) / 10;
            // 头插法
            back = new ListNode(val);
            back.next = temp;
        }
        return back;
    }
}

image-20230703225116512

一次通过,但明显不是最优解,时间和空间复杂度都有可改善的地方。看看官方题解吧

官方题解

emmmmmm看了官方题解,跟我一模一样啊,为啥会才打败这点用户?

class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        Deque<Integer> stack1 = new ArrayDeque<Integer>();
        Deque<Integer> stack2 = new ArrayDeque<Integer>();
        while (l1 != null) {
            stack1.push(l1.val);
            l1 = l1.next;
        }
        while (l2 != null) {
            stack2.push(l2.val);
            l2 = l2.next;
        }
        int carry = 0;
        ListNode ans = null;
        while (!stack1.isEmpty() || !stack2.isEmpty() || carry != 0) {
            int a = stack1.isEmpty() ? 0 : stack1.pop();
            int b = stack2.isEmpty() ? 0 : stack2.pop();
            int cur = a + b + carry;
            carry = cur / 10;
            cur %= 10;
            ListNode curnode = new ListNode(cur);
            curnode.next = ans;
            ans = curnode;
        }
        return ans;
    }
}

image-20230703225437413

???不是跟我基本上一样吗?唯一区别是我用的stack他用的Deque有什么区别吗?竟然差这么多。

源码分析 Deque与Stack

stack

public class Stack extends Vector

stack继承了Vector,我代码的主要操作在push和pop操作,而push和pop实际上调用的是父类的方法 addElement和removeElement。

image-20230703225907813

Vetor呢是对数组Object[]进行操作的并且是线程安全的。初始化时数组大小为10

因为是对数组进行操作,在push方法就存在动态增加数组大小的操作,而pop方法又存在数组复制的操作。

如图:

image-20230703230247829

Deque

初始化时,数组大小为16+1

在添加元素数,可以看到,其实elements类似于一个循环数组

因为push会在头加一个数,dec会计算当前头位置-1是否超出小于0,如果小于0则将新数放到末尾。

image-20230703230904735

如果当头等于末尾时表示循环数组用完了,需要扩容。

下面是pop方法:

image-20230703232041090

其实可以明显看到,弹出方法比stack少了很多开销(少了减少数组大小的逻辑)。

对比结果

  1. 首先Stack是继承的Vector是线程安全的而ArrayDeque线程不安全,少了这部分开销
  2. ArrayDeque是使用的循环数组,在本题中push方法可能不会与Stack上不会有太大差别,最多就是初始大小不同(都是扩容两倍)
  3. ArrayDeque在弹出方法上是很快的,无需减少容量,直接取值,置空,然后计算head即可

综上所述,如果在单线程环境下使用堆栈数据结构,并且对性能要求较高,建议使用ArrayDeque。如果需要在线程安全的环境下使用堆栈,或者需要与遗留代码兼容,则可以选择使用Stack。然而,一般情况下,推荐使用ArrayDeque来实现堆栈,因为它具有更好的性能。

个人博客

http://roe.roewudu12.vip/archives/445.-liang-shu-xiang-jia-ii-dequeyu-stackyuan-ma-jie-xi

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值