Swift && LeetCode 第二题:两数相加

326 阅读3分钟

Offer 驾到,掘友接招!我正在参与2022春招系列活动-刷题打卡任务,点击查看活动详情

今天我们来看看第二题:两数相加

两数相加

题目描述:

给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。请你将两个数相加,并以相同形式返回一个表示和的链表。(你可以假设除了数字 0 之外,这两个数都不会以 0 开头。)

解题思路

在这道题中,首先,提到了逆序存储,我们在做加法运算时,是从个位数开始的,所以在这里我们不需要倒序处理数据存储,但我们要处理的是加法运算中的逢十进一位,也就是进位处理其次,这题考察了我们对链表双指针的操作

需要注意的就是:

  • 获取两个链表对应的长度
  • 对齐相加考虑进位
  • 在较短的链表末尾补零

同时在做链表题时,有个技巧就是使用dummy(虚拟头节点)。好处是,不会影响起始节点的操作,如果不理解这个技巧的话,在之后的链表题中它的作用也会不言而喻。

在这题中,我想到的是使用while循环解法,或者递归解法去实现。

while循环解法

首先创建一个虚拟的头节点dummy,我们把需要操作的链表都放在这个节点下;
然后我们需要记录一个进位数,用来在后面运算。
再然后我们开启一个 while循环,满足条件就是 两个链表都为空,且没有进位时跳出循环
在循环内,处理两个链表中值的加法运算,逢十进一,进位给下一次运算的结果叠加。使用运算后的结果生成一个节点,并将操作的指针切换到下一个节点,进行下一个循环
循环结束后,返回虚拟的头节点下的下一个节点即dummy.next

class Solution {
    func addTwoNumbers(_ l1: ListNode?, _ l2: ListNode?) -> ListNode? {
        var carry = 0
        let dummy = ListNode(-1)
        var newNode = dummy
        var n1: ListNode? = l1
        var n2: ListNode? = l2
        while n1 != nil || n2 != nil || carry > 0 {
            var val = carry
            if let node1 = n1 {
                val += node1.val
                n1 = node1.next
            }
            if let node2 = n2 {
                val += node2.val
                n2 = node2.next
            }
            carry = val / 10
            val = val % 10
            newNode.next = ListNode(val)
            newNode = newNode.next!
        }
        return dummy.next
    }
}

截屏2022-03-05 下午6.45.36.png

递归解法

递归在整体思路上应该也是一样,只是方式不同,如果把运算过程拆成每一位运算,那么使用递归,思路应该就会很清晰。我们的递归过程其实就是运算的过程,只是需要做好运算结束的判断条件即可。

class Solution {
    func addTwoNumbers(_ l1: ListNode?, _ l2: ListNode?) -> ListNode? {
        let dummy = ListNode(-1)
        addTwoNumber(l1, l2, carry: 0, newNode: dummy)
        return dummy.next
    }
    
    func addTwoNumber(_ l1: ListNode?, _ l2: ListNode?, carry: Int, newNode: ListNode)-> ListNode? {
        if l1 == nil, l2 == nil, carry == 0 {
            return nil
        }
        var carry = carry
        var n1: ListNode? = l1
        var n2: ListNode? = l2
        var val = carry
        // 节点1
        if let node1 = n1 {
            val += node1.val
            n1 = node1.next
        }
        // 节点2
        if let node2 = n2 {
            val += node2.val
            n2 = node2.next
        }
        // 这里可以通过整除 或者 减法来处理进位
        carry = val / 10
        val = val % 10
        let nextNode = ListNode(val)
        newNode.next = nextNode
        // 返回下一个节点
        return addTwoNumber(n1, n2, carry: carry, newNode: nextNode)
    }
}

截屏2022-03-05 下午6.42.43.png

总结

针对链表的操作,我们可以优先使用虚拟节点。如果不使用 dummy 虚拟节点,代码会复杂很多,而有了 dummy 节点这个占位符,可以很大程度避免空指针的情况,同时也可以降低代码的复杂性。

针对两数相加,位数运算其实没什么好说的,但是如果顺序是正序的话,我们处理起来就会比倒序要麻烦一些了。