算法修炼之路——【链表】Leetcode 2 两数相加

题目描述

给出两个非空的链表用来表示两个非负的整数。其中,它们各自的位数是按照逆序的方式存储的,并且它们的每个节点只能存储一位数字(即存在进位的情况)。

如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。

示例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,],并令pAlistA的活动指针,类似地,pBlistB活动指针,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. 当存在进位的时候,如何处理?

对于问题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,则会出现空指针的异常,原因是:

  1. 访问最后一个节点时,pB == null,但是我们继续访问了其值,故报错;
  2. 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

发布了47 篇原创文章 · 获赞 55 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/u011106767/article/details/105446950