给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。
如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。
您可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例:
输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807
public class ListNode {
int val;
ListNode next;
ListNode(int x) { val = x; }
}
思路
我的想法:首先将两个链表从头到尾生成两个数字,然后相加,得到的结果再通过条件生成一个链表。测试时发现没有问题,但是运行时报错了。
报错的情况:
l1:[1]
l2:[9,9,9,9,9,9,9,9,9,9]
得到的数字是1410065407,很明显是int类型数值溢出了,当我准备类型换成Long的时候,发现题目的ListNode类是不可以修改的,而ListNode中的值类型是int,所以这就意味着,这个题目是不能通过简单的数值计算完成的,必须要放在容器中进行操作。
public class ListNode {
int val;
ListNode next;
ListNode(int x) { val = x; }
}
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
int num1 = toNum(l1);
int num2 = toNum(l2);
int result = num1 + num2;
ListNode head = new ListNode(result %10);
ListNode tail = head;
result /= 10;
//结果转化成ListNode
while(result > 0){
ListNode node = new ListNode(result % 10);
tail.next = node;
tail = node;
result /= 10;
}
return head;
}
//ListNode转化成数字
public int toNum(ListNode list){
int i = 1,num = 0;
while(list.next != null ){
num = num + i*list.val;
list = list.next;
i = i*10;
}
num = num + i*list.val;
return num;
}
}
接下来我直接想到的是将链表遍历一次,放进一个数组内,在两个数组内每一位相加,超过10就进位,想想好像挺简单。
但是实际上转化成数组再转成链表,中间要考虑的细节特别多。
- 首先链表长度是有限的,转成数组后,超过有效长度默认都是0,所以无论是链表转数组,还是数组转链表,都要用一个变量来存放数据有效长度。方便数组相加时,判断到哪一位结束,不然后面一直进行0+0的操作。
- 因为考虑到可能出现进位,那么结果的有效长度应该是max( l1有效长度, l2有效长度)+1,这个时候问题来了,假设[0,5]+[0,5],那么结果是[0,0,1],没有问题,但是如果是[0,4]+[0,4],那么遍历相加后的结果是[0,8,0],所以最后输出的时候还要对结果进行处理,考虑最后一位是否进位的情况。
- 最后就是数据规模的问题,我一开始数组用的长度是50,后来有一组测试数据报错,因为数据规模超过50。我后来改成200,终于提交通过了。原则上可以将数组调整的更大来处理更大规模的数据。但是这样当数据规模很小时非常浪费空间。(是不是可以使用ArrayList???有空思考一下)
//在链表转数组过程中,存放有效长度
private int arrayLength;
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
int[] nums1 = toArray(l1);
int length1 = this.arrayLength; //nums[1]有效长度
int[] nums2 = toArray(l2);
int length2 = this.arrayLength; //nums[2]有效长度
if(length1 > length2){
//判断是否进位
int outNumber = 0;
int i = 0;
for(;i<length1+1;i++) {
if (nums1[i] + nums2[i] + outNumber >= 10) { //进位
nums1[i] = (nums1[i] + nums2[i]) - 10 + outNumber;
outNumber = 1;
} else { //非进位
nums1[i] = nums1[i] + nums2[i] + outNumber;
outNumber = 0;
}
}
return toListNode(nums1,i);
}else{
int outNumber = 0;
int i = 0;
for(;i<length2+1;i++){
if(nums1[i]+nums2[i] + outNumber >=10 ){
nums2[i] = (nums1[i]+nums2[i])-10 + outNumber;
outNumber = 1;
}else{
nums2[i] = nums1[i]+nums2[i]+outNumber;
outNumber = 0;
}
}
return toListNode(nums2,i);
}
}
/**
* 将数组转成链表
* @param nums
*/
public ListNode toListNode(int[] nums,int length){
ListNode head = new ListNode(nums[0]);
ListNode index = head;
//只处理(数据规模-1)位
for(int i = 1;i<length-1;i++){
index.next = new ListNode(nums[i]);
System.out.println("i"+i);
index = index.next;
}
//再判断最后1位是否为0,不为0,把它添加到链表尾部
if(nums[length-1] != 0){
index.next = new ListNode(nums[length-1]);
}
return head;
}
/** * 将链表转成数组
* @param list
* @return
*/
public int[] toArray(ListNode list){
ListNode index = list;
int[] nums = new int[200];
int length = 0;
while(index != null){
nums[length++] = index.val;
index = index.next;
}
//将全局变量设置成数组有效长度
this.arrayLength = length;
return nums;
}
最优的方法应该是在链表中进行操作
两个链表每一个节点依次相加,有进位就算到下一个节点,如果某个链表结束了,将值设置成0。因为数据规模是最长的链表长度+1,所以循环结束后判断一下最后是否发生了进位,有进位,再补上一个尾节点1。这里有两个小细节:
- 开始的时候新建了一个节点ListNode(0),然后真正的数据在0后面添加,最后返回的时候返回ListNode(0)下一个节点。这样做的好处是避免使用额外的条件判断语句来初始化第一个第一个数据。
- 在判断p = p.next和 q = q.next,我一开始用if(p.next !=null) 作为条件,发现会程序会一直跑。提交显示超时(想不通为什么)。
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode result = new ListNode(0);
ListNode p = l1,q = l2,curr = result;
int carry = 0;
while(p != null || q != null){
int x = p != null ? p.val : 0;
int y = q != null ? q.val : 0;
int sum = x+y+carry;
carry = sum/10;
curr.next = new ListNode(sum%10);
curr = curr.next;
if(p != null){
p = p.next;
}
if(q != null){
q = q.next;
}
}
if(carry > 0){
curr.next = new ListNode(carry);
}
return result.next;
}