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;
}
}
一次通过,但明显不是最优解,时间和空间复杂度都有可改善的地方。看看官方题解吧
官方题解
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;
}
}
???不是跟我基本上一样吗?唯一区别是我用的stack他用的Deque有什么区别吗?竟然差这么多。
源码分析 Deque与Stack
stack
public class Stack extends Vector
stack继承了Vector,我代码的主要操作在push和pop操作,而push和pop实际上调用的是父类的方法 addElement和removeElement。
Vetor呢是对数组Object[]进行操作的并且是线程安全的。初始化时数组大小为10
因为是对数组进行操作,在push方法就存在动态增加数组大小的操作,而pop方法又存在数组复制的操作。
如图:
Deque
初始化时,数组大小为16+1
在添加元素数,可以看到,其实elements类似于一个循环数组
因为push会在头加一个数,dec会计算当前头位置-1是否超出小于0,如果小于0则将新数放到末尾。
如果当头等于末尾时表示循环数组用完了,需要扩容。
下面是pop方法:
其实可以明显看到,弹出方法比stack少了很多开销(少了减少数组大小的逻辑)。
对比结果
- 首先Stack是继承的Vector是线程安全的而ArrayDeque线程不安全,少了这部分开销
- ArrayDeque是使用的循环数组,在本题中push方法可能不会与Stack上不会有太大差别,最多就是初始大小不同(都是扩容两倍)
- ArrayDeque在弹出方法上是很快的,无需减少容量,直接取值,置空,然后计算head即可
综上所述,如果在单线程环境下使用堆栈数据结构,并且对性能要求较高,建议使用ArrayDeque。如果需要在线程安全的环境下使用堆栈,或者需要与遗留代码兼容,则可以选择使用Stack。然而,一般情况下,推荐使用ArrayDeque来实现堆栈,因为它具有更好的性能。
个人博客
http://roe.roewudu12.vip/archives/445.-liang-shu-xiang-jia-ii-dequeyu-stackyuan-ma-jie-xi