持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第1天,点击查看活动详情
题目描述:Q2:两数相加
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例 1:
输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807.
示例 2:
输入:l1 = [0], l2 = [0]
输出:[0]
示例 3:
输入:l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9]
输出:[8,9,9,9,0,0,0,1]
提示:
- 每个链表中的节点数在范围
[1, 100]内 0 <= Node.val <= 9- 题目数据保证列表表示的数字不含前导零
思路 & CODE
1. 直接相加
既然题目都说了两数相加,那就直接把两个数加起来,亏它是一道medium题。再看一下链表范围是[1, 100],也就是说链表中数字最大值是10^100,而int最大值是2^32,long最大值是2^64,既然这些变量都不行,那就直接上大将BigDecimal,说干就干
public ListNode addTwoNumbers02(ListNode l1, ListNode l2) {
ListNode resNode = null;
ListNode headNode = resNode;
String num1 = "";
String num2 = "";
while (l1 != null) {
num1 += l1.val;
}
while (l2 != null) {
num2 += l2.val;
}
BigDecimal bNum1 = new BigDecimal(num1);
BigDecimal bNum2 = new BigDecimal(num2);
bNum1 = bNum1.add(bNum2);
for (char c : bNum1.toPlainString().toCharArray()) {
if (resNode == null) {
resNode = new ListNode(c);
} else {
resNode.next = new ListNode(c);
}
}
return headNode;
}
然而现实是残酷的,虽然没报溢出,但是超时了。
2. 维护一个加法进位变量
题目已经给出了提示,每位数字都是按照逆序以链表的方式存储,这就简单了呀,每次取这两个链表的头节点出来进行相加,然后把相加的数拼到新链表上就行了,唯一需要注意的就是十进制相加需要进位,我们只需要添加一个变量来维护进位就可以了,开干:
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
// 用来维护返回的链表
ListNode resNode = null;
// 用来存储头节点
ListNode headNode = null;
// 进位变量,只会有两个值0、1
int carryNum = 0;
while (l1 != null || l2 != null) {
// 如果链表节点是null就取0,因为另一个链表肯定有值的
int l1Val = l1 == null ? 0 : l1.val;
int l2Val = l2 == null ? 0 : l2.val;
// 链表节点
l1 = l1 == null ? null : l1.next;
l2 = l2 == null ? null : l2.next;
// 链表1 + 链表2 + 进位
int sum = l1Val + l2Val + carryNum;
// 维护新链表节点的值
int nowVal = 0;
// 处理进位和本次要添加到链表中的值
if (sum >= 10) {
carryNum = 1;
nowVal = sum - 10;
} else {
carryNum = 0;
nowVal = sum;
}
// 维护新的链表
if (resNode == null) {
resNode = new ListNode(nowVal);
headNode = resNode;
} else {
resNode.next = new ListNode(nowVal);
resNode = resNode.next;
}
}
// 如果最后进位是1说明链表还需要加一个节点
if (carryNum == 1) {
resNode.next = new ListNode(1);
}
return headNode;
}
一运行,成功击败了100%的提交,虽然成功ac了,但是这种代码越看越丑陋,if写的也太多了,不符合我智慧的人社!
进一步优化
看大佬题解找到了如下代码:
public ListNode addTwoNumbers03(ListNode l1, ListNode l2) {
ListNode root = new ListNode(0);
ListNode cursor = root;
int carry = 0;
while(l1 != null || l2 != null || carry != 0) {
int l1Val = l1 != null ? l1.val : 0;
int l2Val = l2 != null ? l2.val : 0;
int sumVal = l1Val + l2Val + carry;
carry = sumVal / 10;
ListNode sumNode = new ListNode(sumVal % 10);
cursor.next = sumNode;
cursor = sumNode;
if(l1 != null) l1 = l1.next;
if(l2 != null) l2 = l2.next;
}
return root.next;
}
- 1)dummayHead解决头节点何时创建问题
//======dummayHead优化==================
ListNode root = new ListNode(0);
...
...
return root.next
//=======我的代码========================
// 如果头节点是null,就创建头节点
if (resNode == null) {
resNode = new ListNode(nowVal);
headNode = resNode;
} else {
// 创建新的节点
resNode.next = new ListNode(nowVal);
resNode = resNode.next;
}
这么大一段代码直接就给干掉了,之前也学习过dummyHead,但是这里没想到用,以后纠结头节点怎么创建的时候,可以直接套公式——直接使用一个dummyHead
- 2)除法和取余的秒用
//========优化后的代码
int sumVal = l1Val + l2Val + carry;
carry = sumVal / 10;
ListNode sumNode = new ListNode(sumVal % 10);
//========我的代码========================
int sum = l1Val + l2Val + carryNum;
// 维护新链表节点的值
int nowVal = 0;
// 处理进位和本次要添加到链表中的值
if (sum >= 10) {
carryNum = 1;
nowVal = sum - 10;
} else {
carryNum = 0;
nowVal = sum;
}
我用了一堆if判断和10进行比较来算出本次节点的值以及是否进位,但是大佬直接一个除法、一个取余就把问题解决了。以后发生数值运算除法和取余考虑的优先级应该是最高等级。
- 3)if判断和三元表达式
//=======if判断
if(l1 != null) l1 = l1.next;
if(l2 != null) l2 = l2.next;
//=======三元表达式
l1 = l1 == null ? null : l1.next;
l2 = l2 == null ? null : l2.next;
有时候三元表达式并不比if判断简洁
总结
1. 虚拟头节点
以后纠结头节点怎么创建的时候,可以直接套公式——直接使用一个dummyHead来解决何时创建头节点的问题,最后可以return head.next返回正常头节点
2. 除法和取余
发生数值运算时,除法和取余需要被考虑的优先级应该是最高的。