题目
- 给出两个非空的链表用来表示两个非负整数。其中,他们各自的位数是按照逆序的方式进行存储的,并且他们的每个节点只能存储一位数字。
- 如果,我们将这两个数字加起来,则会返回一个新的链表来表示他们的和。
- 可以假设除了数字0以外,这两个数都不会以0开头
示例
输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807
解析
- 利用直接遍历的方式计算,参考了leetcode中的解析
- 这里我们自己实现了一种解析,是用Map来实现的,key是位数,value是两者相加的和,比较繁琐,不推荐,但是是自己当时想到的。
方法一:
- 这道题其实就是两个数字进行相加求和,我们从最低位开始相加,也就是我们l1和l2的表头开始相加,因为他们是逆序排序的。由于每个数字都应该在0-9之间,所以我们计算两个数字的时候可能会出现“溢出”。例如:5 + 7 = 12。这种情况下,我们呢应该将当前位的值设置成2,并且将进位carry = 1代入到下一次的迭代。进位carry必定是0或者1,这是因为两个数字相加(考虑到进位)可能出现最大的和是9 + 9 + 1 = 19
- 伪代码如下
- 将当前节点初始化为返回列表的哑结点
- 将进位carry初始化为0
- 将p和q分别初始化为列表l1和l2的头部
- 遍历列表l1和l2直至到达他们的尾端
(1)将x设置为节点p的值。如果p已经达到了l1的尾端,则将其设置为0
(2)将y设置为节点q的值。如果q已经到达了l2的尾端,则将其设置为0
(3)设定sum = x + y + carry
(4)更新进位的值,carry = sum/ 10
(5)创建一个数值是(sum % 10)的新节点,并将其设置为当前节点的下一个节点,然后将当前节点前进到下一个节点上。
(6)同时,将p和q前进到下一个节点 - 检查carry = 1是否成立,如果成立,则向返回列表追加一个含有数字1的新节点
- 返回哑结点的下一个节点。
代码
class ListNode {
int val;
ListNode next;
ListNode(int x) {
val = x;
}
}
/**
* 第二种优化的方法,直接遍历,不再使用map进行中间环节的保存
* @param l1
* @param l2
* @return
*/
public static ListNode addTwoNumbers2(ListNode l1, ListNode l2) {
// 最终要返回的节点
ListNode dummyHead = new ListNode(0);
ListNode p = l1;
ListNode q = l2;
ListNode curr = dummyHead;
// 进位
int curry = 0;
while (p != null || q != null) {
// 如果l1或者l2不为空
// 如果当前l1的节点不为空,则取节点的值,否则就用0代替
int x = p != null ? p.val : 0;
// 如果当前l2的节点不为空,则取节点的值,否则就用0代替
int y = q != null ? q.val : 0;
// 计算总和,记得这里一定要加一个进位
int sum = x + y + curry;
// 判断当前的总和是否有进位
curry = sum / 10;
// 给结果节点中赋值
curr.next = new ListNode(sum % 10);
curr = curr.next;
if (p != null) {
p = p.next;
}
if (q != null) {
q = q.next;
}
}
if (curry > 0) {
// 表示有进位,需要新建一个节点
curr.next = new ListNode(curry);
}
return dummyHead.next;
}
复杂度分析
- 时间复杂度:O(max(m,n)),假设m和n分别表示l1和l2的长度,上面的算法最多重复max(m, n)次
- 空间复杂度:O(max(m,n)),新列表的长度最多就是max(m,n) + 1
方法二
- 这个方法就是我自己随便写出来的,不推荐使用
class ListNode {
int val;
ListNode next;
ListNode(int x) {
val = x;
}
}
/**
* 这个解法的思路是:
* 1. 将传进来的l1和l2转换成map,key是当前第几位 value是当前位数上的值
* 2. 找出l1和l2最大的值,遍历
* 3. 依次取出两个map中对应的位数,若map不足,则取0替代
* 4. 二者相加,并且加上上一次计算的进位
* 5. 若遍历结束后,进位是1,则表示新增加一位,则新增一个节点。
* 6. 将最终map的结果放到ListNode中返回。
* @param l1
* @param l2
* @return
*/
public static ListNode addTwoNumbers(ListNode l1, ListNode l2) {
// 当前l1的下标(l1的第几个节点)
int index1 = 1;
// 当前l2的下标(l2的第几个节点)
int index2 = 1;
// 用来保存l1遍历的结果,key是当前第几个节点,value是当前的值即节点的val值
Map<Integer, Integer> map1 = new HashMap<>();
// 用来保存l2遍历的结果,key是当前第几个节点,value是当前的值即节点的val值
Map<Integer, Integer> map2 = new HashMap<>();
// 用来保存最终相加完成的结果,key是当前第几个节点,value是当前的值即节点的val值
Map<Integer, Integer> result = new HashMap<>();
// 开始遍历l1 对其map1进行赋值
while (l1 != null) {
map1.put(index1, l1.val);
index1 ++;
l1 = l1.next;
}
// 开始遍历l2,对其map2进行赋值
while (l2 != null) {
map2.put(index2, l2.val);
index2 ++;
l2 = l2.next;
}
// 用来保存是否有进位
int up = 0;
if (index1 > index2) {
// 当参数l1的长度大于l2的时候,遍历l1
for (int i = 1; i < index1; i++) {
Integer value1 = map1.get(i);
// 位数不足则用0替代
Integer value2 = map2.getOrDefault(i, 0);
// 记得加的时候加上进位
int value = value1 + value2 + up;
result.put(i, value % 10);
// 若除以10不是0则表示有进位
if (value / 10 != 0) {
up = 1;
} else {
up = 0;
}
}
} else {
// 参考上面的注解
for (int i = 1; i < index2; i++) {
Integer value1 = map1.getOrDefault(i, 0);
Integer value2 = map2.get(i);
int value = value1 + value2 + up;
result.put(i, value % 10);
if (value / 10 != 0) {
up = 1;
} else {
up = 0;
}
}
}
// 如果当前注解不为0 表示有一个进位,应该新增一个节点
if (up == 1) {
result.put(Math.max(index1, index2), up);
}
// 下面的操作是将result最终结果的map转为ListNode
ListNode root = new ListNode(0);
ListNode tmp = root;
for (int i = 1; i < Math.max(index1, index2); i++) {
ListNode listNode = new ListNode(result.get(i));
tmp.next = listNode;
tmp = listNode;
}
// 如果最高位有进位,新增一个节点
if (result.getOrDefault(Math.max(index1, index2), 0) != 0) {
ListNode listNode = new ListNode(result.get(Math.max(index1, index2)));
tmp.next = listNode;
}
return root.next;
}