题目描述
给出两个非空的链表用来表示两个非负的整数。其中,它们各自的位数是按照逆序的方式存储的,并且它们的每个节点只能存储一位数字(即存在进位的情况)。
如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。
示例1:
输入: listA = [2, 4, 3]; listB = [5, 6, 4]
输出: [7, 0, 8]
解释: 342 + 465 = 807
示例2:
输入: listA = [8, 4, 6]; listB = [5, 6, 4]
输出: [1, 1, 1, 3]
解释: 648 + 465 = 1113
思路分析
我们可以将这道题与加法直接进行联系,12 + 234 = 246, 0 + 123 = 123
,这里我们先考虑一般情况下的解题方法,这里我们给出图示:
图1
我们假设给出的两个原始链表listA = [1, 2, 3], listB = [4, 5, 6,]
,并令pA
为listA
的活动指针,类似地,pB
为listB
活动指针,listOutput
为输出链表,这里注意输出链表的指针方向与原始链表相向。
易知,图1中不存在进位,当两个指针指向各自链表的第一个节点时,得出listOutput
的尾节点5
,第二个节点相加得出listOutput
的中间节点值,最后两个链表的尾节点值相加得到listOutput
的头节点值。
这里我们直接通过哨兵机制实现倒序插入节点:
代码1: 输出链表倒序插入
// sum already obtained
ListNode dummyHead = new ListNode(0);
ListNode newNode = new ListNode(sum)
newNode.next = dummyHead.next;
dummyHead.next = newNode;
解决了倒序插入输出链表功能后,我们将分析过程整合,通过代码表达:
代码2: 等长链表节点对应相加后,并插入输出链表指定位置
ListNode dummyHead ;
ListNode pA = listA;
ListNode pB = listB;
while(pA != null && pB != null){
int sum = pA.val + pB.val;
ListNode newNode = new ListNode(sum);
newNode.next = dummyHead.next;
dummyHead.next = newNode;
pA = pA.next;
pB = pB.next;
}
边界条件考虑
这里,我们在思路分析中给出了一般规则,但是更重要的是边界问题的统一化,这里我们需要进一步测试以下边界问题:
- 当两个链表不等长时的处理?
- 当存在进位的时候,如何处理?
对于问题1,我们来细致的讨论。
图2
这里我们通过一般规则的代码2来进行分析:
代码2: 等长链表节点对应相加后,并插入输出链表指定位置
ListNode dummyHead ;
ListNode pA = listA;
ListNode pB = listB;
while(pA != null && pB != null){
int sum = pA.val + pB.val;
ListNode newNode = new ListNode(sum);
newNode.next = dummyHead.next;
dummyHead.next = newNode;
pA = pA.next;
pB = pB.next;
}
当while
循环体运行第3
次的时候,此时pA -> ListNode(3), pB -> ListNode(6)
, 此时依然满足pA != null && pB != null
,得出ListNode(9)
;
故进入第4
次循环,则有pA -> ListNode(3), pB -> null
,此时循环终止,但是我们清晰地看到,listA
还存未被访问的节点,故一般规则需要进一步改善。
这里我们可以将while
的循环条件扩大一些,比如当pA, pB
至少有一个不为null
就继续运行,即while(pA != null || pB != null)
.
此时我们如果按照图2的例子运行代码2,则会出现空指针的异常,原因是:
- 访问最后一个节点时,
pB == null
,但是我们继续访问了其值,故报错; - 在
pB == null
时,在访问pB.next
时也会报错;
这时候我们需要对循环体进行改进:
代码3:
ListNode dummyHead ;
ListNode pA = listA;
ListNode pB = listB;
while(pA != null || pB != null){ // change this line
int sum = ((pA == null ? 0 : pA.val)
+ (pB == null ? 0 : pB.val) ); //change this line
ListNode newNode = new ListNode(sum);
newNode.next = dummyHead.next;
dummyHead.next = newNode;
pA = pA == null ? null : pA.next; // change this line
pB = pB == null ? null : pB.next; // change this line
}
对于问题2,同样地,我们将通过图示3进行分析:
图3
我们将数据更新为listA = [7, 8, 9, 3], listB = [4, 5, 6,]
,则会发生进位,这里我们将rmdr
设置为余数,carry
设置为进位,sum
为当前节点的最终数值。当两个指针计算原始链表的首节点时,此时carry = 0
,故sum = rmdr
; 之后每一位均有进位。由于图3逻辑较清晰,我们直接给出此完整的解题代码:
代码4:
ListNode pA = listA;
ListNode pB = listB;
ListNode dummyHead = new ListNode(0);
int carry = 0; // carry bit
int sum;
int curr;
while(pA != null || pB != null || carry != 0){
sum = (pA == null ? 0 : pA.val)
+ (pB == null ? 0 : pB.val)
+ carry;
curr = sum % 10;
carry = sum / 10;
// insert newNode into dummyHead's behind
ListNode newNode = new ListNode(curr);
newNode.next = dummyHead.next;
dummyHead.next = newNode;
pA = pA == null? null : pA.next;
pB = pB == null? null : pB.next;
}
到此,我们已经讨论了一般规则与边界情况,此时可直接给出代码如下:
解题代码
public static ListNode solution(ListNode listA, ListNode listB) {
/* Step1:
Init. pointers and integers
*/
ListNode pA = listA;
ListNode pB = listB;
ListNode dummyHead = new ListNode(0);
int carry = 0; // carry bit
int sum;
int curr;
/* Step2: go through linkedlist
and
put value to output-linkedlist
*/
while(pA != null || pB != null || carry != 0){
sum = (pA == null ? 0 : pA.val)
+ (pB == null ? 0 : pB.val)
+ carry;
curr = sum % 10;
carry = sum / 10;
// insert newNode into dummyHead's behind
ListNode newNode = new ListNode(curr);
newNode.next = dummyHead.next;
dummyHead.next = newNode;
pA = pA == null? null : pA.next;
pB = pB == null? null : pB.next;
}
return dummyHead.next;
}
复杂度分析
这里设listA
包含m
个节点; listB
包含n
个节点。
时间复杂度:我们对两条链表分别进行了一次遍历,则为O(m) + O(n)
, 故时间复杂度为O(max(m, n))
;
空间复杂度:我们存在额外的链表的辅助,总长度为max(m, n)
,但存在一个哨兵节点,故空间复杂度为O(max(m, n))
,输出链表长度为max(m, n) + 1
GitHub源码
完整可运行文件请访问GitHub。