力扣题目地址: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.)
- 取出两个链表节点值
- 节点值求和,并放入新节点。
- 移动链表指针,指向链表下一个节点。
代码实现如下:
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.)
- 余数值当然是放在当前新建的链表节点。
- 进位值我们需要在下一个循环中加到求和值上面去。
- 因为2可知我们需要将进位值带入下一个循环,这个进位值我们就不能放在循环体中了,我们需要放在循环体外面。
- 如果最后一位数字存在进位,没有下一次循环了,我们需要再额外添加一个节点存放进位值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。
不忘初心,砥砺前行。