LeetCode-数据结构-两数相加

力扣题目地址:https://leetcode-cn.com/problems/add-two-numbers/

首先看题目:
给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。
如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。
您可以假设除了数字 0 之外,这两个数都不会以 0 开头。

示例:
输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807

解决思路:
首先明白主线任务:我们要将两个链表的相同节点的值求和,然后新建一个节点把值放进去。这就是我们的主要思路。
至于其他的东西可以遇上了在考虑;因为对于我们这种刚开始练习的新手来说,一下子思考太多的因素会把这个问题变得太过复杂;我们可以一步一步来解决问题。

实践代码:
首先加入题目中给的 ListNode 类

package com.gyx.util;

/**
 * 节点类
 * @author GeYuxuan 2020/02/29 17:09
 */
public class ListNode {

    /**
     * 节点值
     */
    int val;

    /**
     * 下一个节点引用
     */
    ListNode next;

    /**
     * 构造函数,初始化设置val值
     * @param x
     */
    ListNode(int x){
        val = x;
    }
}

然后我们开始我们的解题之旅:
首先,通过观察上面的 ListNode 类,我们知道链表(单向链表,以下统称链表)一共有两个属性:val 是当前的链表节点的值,next 是下一个节点的引用。
根据主线任务,我们需要先取出每个节点的值,然后相加,放入一个新的节点。

因为链表不知道有几个元素,所以我们无法用for循环来进行遍历,只能使用while语句来遍历。我们思考一下终结条件是什么?肯定是当前节点如果为null的话,我们就可以退出了。这个时候我们需要多考虑一些了,l1和l2两个链表的长度是不是有可能不一样?有可能l1遍历完了,l2还有节点存在。所以我们是不是要对两个链表都做校验?并且还是或连接符,只要有一个链表的节点不为空,我们就需要继续往下遍历。

链表遍历的情况考虑好之后,我们再来考虑具体实现细节:(对应代码1.2.3.)

  1. 取出两个链表节点值
  2. 节点值求和,并放入新节点。
  3. 移动链表指针,指向链表下一个节点。

代码实现如下:

package com.gyx.util;

/**
 * 两数相加  v1版
 * @author GeYuxuan 2020/03/01 13:40
 */
public class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        while(l1 != null || l2 != null){
            //1.取出 l1和l2当前节点的值,如果当前节点为空则补0
            int x = l1 == null ? 0 : l1.val;
            int y = l2 == null ? 0 : l2.val;
            
            //2.将两节点值求和并增加一个新节点
            int sum = x + y;
            //存在问题:新建的当前节点如何指向下一个节点?
            ListNode cur= new ListNode(sum);
            
            //3.移动l1和l2的指针
            if(l1 != null){
                l1 = l1.next;
            }
            if(l2 != null){
                l2 = l2.next;
            }
        }
        //存在问题:该返回哪个节点?
        return cur;
    }
}

第一版遇到的问题:新建的当前节点如何指向下一个节点?我们该返回哪个节点?

我们在求和设置新节点时,遇到了新建节点的指向问题。我们这时候发现在方法中我们没有新建一个新的链表来填充求和值。所以我们需要在方法外面新建一个新的链表。

	//新建返回链表头结点
	ListNode cur = new ListNode();
	while(l1 != null || l2 != null){
		   //1.取出 l1和l2当前节点的值,如果当前节点为空则补0
		   int x = l1 == null ? 0 : l1.val;
		   int y = l2 == null ? 0 : l2.val;
		   
		   //2.将两节点值求和并增加一个新节点
		   int sum = x + y;
		   //存在问题:新建的当前节点如何指向下一个节点?(解决)
		   //增加一个新节点
		   cur.next = new ListNode(sum);
		   cur = cur.next;
		   
		   //3.移动l1和l2的指针
		   if(l1 != null){
		       l1 = l1.next;
		   }
		   if(l2 != null){
		       l2 = l2.next;
		   }
		}
		//存在问题:该返回哪个节点?(未解决)
		return cur;
    }

上面的代码解决了新建节点的指向问题,但是没有解决我们的第二个问题:我们怎么返回求和链表的头结点呢?因为每次新建节点,都会把 cur 节点的指针往后移一位。所以我们返回的cur节点是最后一个节点,这样的链表也就只有一位数字了。

所以我们需要增加一个pre虚拟头结点,来保证这个虚拟头结点的指针永远指向返回链表的第一位数字节点。

package com.gyx.util;

/**
 * 两数相加  v2版
 * @author GeYuxuan 2020/03/01 13:40
 */
public class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
    	//新链表虚拟头结点
        ListNode pre = new ListNode(0);
        ListNode cur = pre;
        
        while(l1 != null || l2 != null){
            //1.取出 l1和l2当前节点的值,如果当前节点为空则补0
            int x = l1 == null ? 0 : l1.val;
            int y = l2 == null ? 0 : l2.val;
            
            //2.将两节点值求和并增加一个新节点
            int sum = x + y;
            //存在问题:新建的当前节点如何指向下一个节点?(解决)
            //增加一个新节点
            cur.next = new ListNode(sum);
            cur = cur.next;
            
            //3.移动l1和l2的指针
            if(l1 != null){
                l1 = l1.next;
            }
            if(l2 != null){
                l2 = l2.next;
            }
        }
        //存在问题:该返回哪个节点?(解决,返回虚拟头结点的下个节点)
        return pre.next;
    }
}

解决完发现的问题后,我们离成功不远了。我们接下来就可以测试了,我们可以自己写测试数据来进行测试。

package com.gyx.util;

/**
 * 测试数据
 * @author GeYuxuan 2020/02/29 17:35
 */
public class Test {

    public static void main(String[] args) {
        //设置 l1,l2 链表  321 + 321 = 642
        ListNode l1 = new ListNode(1);
        l1.next = new ListNode(2);
        l1.next.next = new ListNode(3);
        ListNode l2 = new ListNode(1);
        l2.next = new ListNode(2);
        l2.next.next = new ListNode(3);

        Solution solution = new Solution();
        ListNode result = solution.addTwoNumbers(l1,l2);
        while (result != null){
            System.out.println(result.val);
            result = result.next;
        }
    }
}

运行之后我们会得到打印的数字:2,4,6,这不就是我们要的结果吗?我们成功了。

但是这个简易版的是存在漏洞的,什么漏洞呢?你们有没有考虑到进位的问题?简单来说,刚刚的数字都是相加小于10的数字;如果相加大于10的话,我们的链表节点就会出现两位数。

    public static void main(String[] args) {
        //设置 l1,l2 链表  342 + 465 = 807
        ListNode l1 = new ListNode(2);
        l1.next = new ListNode(4);
        l1.next.next = new ListNode(3);
        ListNode l2 = new ListNode(5);
        l2.next = new ListNode(6);
        l2.next.next = new ListNode(4);

        Solution solution = new Solution();
        ListNode result = solution.addTwoNumbers(l1,l2);
        while (result != null){
            System.out.println(result.val);
            result = result.next;
        }
    }

这个运行之后的打印的数字就变成:7,10,7。这肯定是有问题的,我们计算的和应该是807。我们从这里可以看出,我们没有考虑进位的问题,这里的10的1应该加载7上面,这样就是 807 了。

第二版遇到的问题:没有考虑进位问题,遇到超过10的数字,我们需要往前进一位。

首先,我们考虑进位之后,肯定是在10~18之间,因为两个最大的一位正整数就是9.两个加起来也就是18。那我们该怎么取这两位数字呢?很简单,个位用取模%,十位用除号 /。

int sum = x + y;
//取进位值 进位值都为1。
int carry = sum / 10;
//取余数值 0~8
sum = sum % 10;

得到两个值之后我们要考虑,如何将这两个值放入链表中。(对应代码1.2.3.4.)

  1. 余数值当然是放在当前新建的链表节点。
  2. 进位值我们需要在下一个循环中加到求和值上面去。
  3. 因为2可知我们需要将进位值带入下一个循环,这个进位值我们就不能放在循环体中了,我们需要放在循环体外面。
  4. 如果最后一位数字存在进位,没有下一次循环了,我们需要再额外添加一个节点存放进位值1.
package com.gyx.util;

/**
 * 两数相加 v3版
 * @author GeYuxuan 2020/03/01 13:40
 */
public class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        //新链表虚拟头结点
        ListNode pre = new ListNode(0);
        ListNode cur = pre;

        //3.进位值放在循环体外
        int carry = 0;

        while(l1 != null || l2 != null){
            //取出 l1和l2当前节点的值,如果当前节点为空则补0
            int x = l1 == null ? 0 : l1.val;
            int y = l2 == null ? 0 : l2.val;

            //将两节点值求和 (增加进位值)
            int sum = x + y +carry;
            //1.取余数
            sum = sum % 10;
            //2.取进位值
            carry = sum / 10;
            //增加一个新节点
            cur.next = new ListNode(sum);
            cur = cur.next;

            //移动l1和l2的指针
            if(l1 != null){
                l1 = l1.next;
            }
            if(l2 != null){
                l2 = l2.next;
            }
        }
        //4.如果最后一次循环,存在进位
        if(carry == 1){
            cur.next = new ListNode(carry);
        }
        return pre.next;
    }
}

好了,这就是完全体的题解了。接下来让我们再去试试这个新玩具吧;试试上面的342+465=807。

public static void main(String[] args) {
        //设置 l1,l2 链表  342 + 465 = 807
        ListNode l1 = new ListNode(2);
        l1.next = new ListNode(4);
        l1.next.next = new ListNode(3);
        ListNode l2 = new ListNode(5);
        l2.next = new ListNode(6);
        l2.next.next = new ListNode(4);

        Solution solution = new Solution();
        ListNode result = solution.addTwoNumbers(l1,l2);
        while (result != null){
            System.out.println(result.val);
            result = result.next;
        }
    }

运行结果打印:7,0,8。
再试试 555 + 555 = 1110;

public static void main(String[] args) {
        //设置 l1,l2 链表  555 + 555 =1110
        ListNode l1 = new ListNode(5);
        l1.next = new ListNode(5);
        l1.next.next = new ListNode(5);
        ListNode l2 = new ListNode(5);
        l2.next = new ListNode(5);
        l2.next.next = new ListNode(5);

        Solution solution = new Solution();
        ListNode result = solution.addTwoNumbers(l1,l2);
        while (result != null){
            System.out.println(result.val);
            result = result.next;
        }
    }

运行之后打印:0,1,1,1。

不忘初心,砥砺前行。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章