力扣-2.两数相加
1.题目介绍
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例 1:
输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807.
示例 2:
输入:l1 = [0], l2 = [0]
输出:[0]
示例 3:
输入:l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9]
输出:[8,9,9,9,0,0,0,1]
提示:
每个链表中的节点数在范围 [1, 100] 内
0 <= Node.val <= 9
题目数据保证列表表示的数字不含前导零
2.题解
一.C代码
struct ListNode* addTwoNumbers(struct ListNode* l1, struct ListNode* l2){
int temp, count = 0;
struct ListNode* p1 = l1, *p2 =l2,*prev = NULL;
while(p1 && p2){
temp = p1->val + p2->val + count;
if (temp >=10) {count = 1; temp = temp -10;}
else count = 0;
p1->val = temp;
prev = p1; // 保存前一个节点
p1 = p1->next;
p2 = p2->next;
}
if (!p1){
prev->next = p2;
}
while(prev->next){
prev = prev->next;
temp = prev->val + count;
if (temp >=10) {count = 1; temp = temp -10;}
else count = 0;
prev->val = temp;
}
if (count) {
struct ListNode* node = (struct ListNode*)malloc(sizeof(struct ListNode));
prev->next = node;
node->next = NULL;
node->val = 1;
}
return l1;
}
题解:
struct ListNode* addTwoNumbers(struct ListNode* l1, struct ListNode* l2) {
struct ListNode *head = NULL, *tail = NULL;
int carry = 0;
while (l1 || l2) {
int n1 = l1 ? l1->val : 0;
int n2 = l2 ? l2->val : 0;
int sum = n1 + n2 + carry;
if (!head) {
head = tail = malloc(sizeof(struct ListNode));
tail->val = sum % 10;
tail->next = NULL;
} else {
tail->next = malloc(sizeof(struct ListNode));
tail->next->val = sum % 10;
tail = tail->next;
tail->next = NULL;
}
carry = sum / 10;
if (l1) {
l1 = l1->next;
}
if (l2) {
l2 = l2->next;
}
}
if (carry > 0) {
tail->next = malloc(sizeof(struct ListNode));
tail->next->val = carry;
tail->next->next = NULL;
}
return head;
}
总结:
1.选取一个变量记录进位
2.我这里直接利用了之前便有的链表l1,在他的基础上改值,第一次循环到一个链表结尾。然后进行一个判断,如果l1结束,将l2的剩余部分接上去,然后再改值;如果l1没有结束,就在l1的基础上继续改值,操作同前。(这里我出现了一个小错误,如果l1结束,这里p1指向的不是最后一个节点,而是NULL,就不能直接用p1来接上p2剩余部分,要设计一个prev存取链表l1的最后一个节点)
3.这一题最容易错的是没有考虑到最后一个节点还能进位的情况,这样就会多出一个节点,需要单独进行判断。
4.题解这里设置比较巧妙,他的循环结束是直到两个链表都到结尾处,使用一个三元运算符来判断加值,并且单独设立了一个链表来存储结果,并分别设置头指针和尾指针,将数据不断往上加,但也要注意到最后一个节点进位的情况。
二.C++代码
核心代码(最省空间)
- 我们可以使用 l1||l2 作为终止条件, 新链表存在l1中, 这样必然运行至遍历完两条链表为止;
如果l1 > l2(长度), 那我们直接向后运行即可(l2 == nullptr后就不再next,默认其值为0)
如果l1 < l2(长度), 那我们在l1==nullptr的时候,就要开始不断创建新节点(这就要求记录prev), 保证与l2同步了
最后记得处理可能最高位的进位即可
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode* head = l1;
ListNode* pre = nullptr;
int carry = 0; // 进位标志
while (l1 || l2) {
int sum = (l1 ? l1->val : 0) + (l2 ? l2->val : 0) + carry;
carry = sum / 10; // 计算进位
if (l1 == nullptr) {
pre->next = new ListNode(sum % 10);
pre = pre->next;
} else {
l1->val = sum % 10;
pre = l1;
l1 = l1->next;
}
if (l2)
l2 = l2->next;
}
if (carry)
pre->next = new ListNode(carry);
return head; // 返回结果链表的头结点
}
};
- 我们这里采用l1&&l2作为终止条件,只要有一个链表遍历完就退出循环,由于我们采用l1接收新链表,这里有两种情况
如果是l2nullptr, 那么我们对于剩余的l1继续操作即可
如果是l1nullptr, 那么我们将剩余的l2接到l1的末尾, 然后对于剩余的l1继续操作即可
这种方法比起上面减少了创建新节点的资源消耗!
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode* head = l1;
ListNode* pre = nullptr;
int carry = 0; // 进位标志
while (l1 && l2) {
int sum = (l1 == nullptr ? 0 : l1->val) + (l2 == nullptr ? 0 : l2->val) + carry;
carry = sum / 10;
l1->val = sum % 10;
pre = l1;
l1 = l1->next;
l2 = l2->next;
}
if (l2) {
pre->next = l2;
l1 = l2; // 这里本来pre->next = l1, 现在=l2, 就跟原来的最后一个l1==nullptr断掉了,我们要更新l1
}
while (l1) {
int sum = l1->val + carry;
carry = sum / 10;
l1->val = sum % 10;
pre = l1;
l1 = l1->next;
}
if (carry)
pre->next = new ListNode(carry);
return head; // 返回结果链表的头结点
}
};
核心代码(最省时间)
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode dummy(0); // 创建一个虚拟头结点,简化链表操作
ListNode* current = &dummy; // 用于遍历链表
int carry = 0; // 进位标志
while (l1 || l2 || carry) {
int sum = (l1 ? l1->val : 0) + (l2 ? l2->val : 0) + carry;
carry = sum / 10; // 计算进位
current->next = new ListNode(sum % 10); // 创建新结点
current = current->next;
if (l1) l1 = l1->next;
if (l2) l2 = l2->next;
}
return dummy.next; // 返回结果链表的头结点
}
};
完整代码(包含测试用例)
#include <iostream>
#include <vector>
using namespace std;
struct ListNode {
int val;
ListNode* next;
ListNode() : val(0), next(nullptr) {}
ListNode(int x) : val(x), next(nullptr) {}
ListNode(int x, ListNode* next) : val(x), next(next) {}
};
ListNode* createLinkedList(const std::vector<int>& values) {
if (values.empty()) {
return nullptr;
}
ListNode* head = new ListNode(values[0]);
ListNode* current = head;
for (int i = 1; i < values.size(); i++) {
current->next = new ListNode(values[i]);
current = current->next;
}
return head;
}
void deleteLinkedList(ListNode* head) {
while (head) {
ListNode* temp = head;
head = head->next;
delete temp;
}
}
void printLinkedList(ListNode* head) {
ListNode* current = head;
while (current != nullptr) {
std::cout << current->val;
if (current->next != nullptr) {
std::cout << " -> ";
}
current = current->next;
}
std::cout << std::endl;
}
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode* head = l1;
ListNode* pre = nullptr;
int carry = 0; // 进位标志
while (l1 || l2) {
int sum = (l1 ? l1->val : 0) + (l2 ? l2->val : 0) + carry;
carry = sum / 10;
if (l1 == nullptr) {
pre->next = new ListNode(sum % 10);
pre = pre->next;
} else
l1->val = sum % 10;
if (l1) {
pre = l1;
l1 = l1->next;
}
if (l2) l2 = l2->next;
}
if (carry) pre->next = new ListNode(carry);
return head;
}
};
int main() {
std::vector<int> values1 = {2, 4, 9};
std::vector<int> values2 = {5, 6, 4, 9};
ListNode* l1 = createLinkedList(values1);
ListNode* l2 = createLinkedList(values2);
Solution solution;
ListNode* result = solution.addTwoNumbers(l1, l2);
printLinkedList(result);
// 释放内存
deleteLinkedList(l1);
deleteLinkedList(l2);
return 0;
}
总结
这里要总结的主要有以下几个思路
1.这里最简单的思路其实是创建第三个链表进行存储,比较简单,,就不详细叙述了
2.在为了节省空间的同时,想要直接利用现成已有的链表节点,即l1
3.这里又分为几种情况:
l1的长度大于等于l2,这个时候除了最后一次若有进位需要创建新节点,其余均可利用l1现有节点即可
l1的长度小于l2,这里要么在之后均创建新的节点,要么直接接上l2后续的节点(经过测试两种方法空间利用差别不大,但第二种方法过于耗时)
这里注意一下为什么需要pre前置节点指针?因为当l1 = l1 -> next; 使得l1 == nullptr时,你直接使用l1 = new ListNode(...);只会使l1指向别的空间节点,但是实际上整个链表末尾指向的还是nullptr。(整个链表未变,只变了一个指针)
注意最后一个进位可能导致的节点数增加即可!