一、题目是啥?一句话说清
给定一个单链表,每个节点代表一个数字位,链表头是最高位,表示一个非负整数。将这个整数加1,并返回结果链表。
示例:
- 输入:
1 -> 2 -> 3表示数字123,加1后变成124,输出1 -> 2 -> 4 - 输入:
9 -> 9表示99,加1后变成100,输出1 -> 0 -> 0
二、解题核心
从最低位开始加1,处理进位问题。由于链表头是最高位,我们需要反向遍历链表,但单链表只能正向遍历,所以可以使用递归或反转链表的方法来模拟从低位到高位的操作。
关键点:处理进位,特别是当所有位都是9时,需要添加新节点作为最高位。
三、关键在哪里?(3个核心点)
想理解并解决这道题,必须抓住以下三个关键点:
1. 反向遍历链表
- 是什么:加1操作从最低位开始,所以我们需要先访问链表的最后一个节点,然后向前处理进位。
- 为什么重要:因为进位是从低位向高位传播的,如果不反向遍历,无法正确处理进位。例如,数字999加1,进位需要从最低位逐步传递到最高位。
2. 处理进位
- 是什么:当某一位加1后等于10时,产生进位,当前位变为0,进位1加到更高位。
- 为什么重要:如果不处理进位,结果会错误。特别是连续进位的情况,如999加1变成1000,需要连续处理多个进位。
3. 可能添加新节点
- 是什么:如果最高位有进位,需要在链表头部添加一个新节点(值为1)。
- 为什么重要:否则,结果会缺少最高位,例如99加1应为100,如果没有新节点,链表只能表示00。
四、解法步骤(通俗理解版本)
- 递归到链表末尾:通过递归函数先到达链表最后一个节点,模拟反向遍历。
- 从后往前加1:在递归返回的过程中,对每个节点加上进位值,并计算新的进位。
- 检查最高位进位:如果递归完成后还有进位,则在链表头部添加一个新节点。
这就像我们做加法竖式时从右往左计算一样,递归帮我们实现了“从右往左”的效果。
五、C++代码实现(附详细注释)
#include <iostream>
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) {}
};
class Solution {
public:
ListNode* plusOne(ListNode* head) {
int carry = helper(head); // 递归处理链表,返回进位
if (carry == 1) { // 如果还有进位,添加新节点
ListNode* newHead = new ListNode(1);
newHead->next = head;
return newHead;
}
return head;
}
// 递归函数:返回进位值
int helper(ListNode* node) {
if (node == nullptr) return 1; // 到达链表末尾,返回1表示加1
int carry = helper(node->next); // 递归到下一个节点,获取进位
int sum = node->val + carry; // 当前节点值加上进位
node->val = sum % 10; // 更新当前节点值
return sum / 10; // 返回新的进位
}
};
// 辅助函数:打印链表
void printList(ListNode* head) {
while (head != nullptr) {
cout << head->val;
head = head->next;
}
cout << endl;
}
// 测试代码
int main() {
Solution solution;
// 示例1: 1->2->3
ListNode* head1 = new ListNode(1);
head1->next = new ListNode(2);
head1->next->next = new ListNode(3);
cout << "输入: ";
printList(head1);
ListNode* result1 = solution.plusOne(head1);
cout << "输出: ";
printList(result1); // 期望输出: 124
// 示例2: 9->9
ListNode* head2 = new ListNode(9);
head2->next = new ListNode(9);
cout << "输入: ";
printList(head2);
ListNode* result2 = solution.plusOne(head2);
cout << "输出: ";
printList(result2); // 期望输出: 100
return 0;
}
六、时间空间复杂度
- 时间复杂度:O(n),其中n是链表长度。递归遍历链表一次。
- 空间复杂度:O(n),由于递归调用栈的深度为n,需要O(n)的栈空间。
七、注意事项
- 递归深度:如果链表非常长(例如数万节点),递归可能导致栈溢出。在这种情况下,可以使用迭代方法(如反转链表)来避免递归。
- 进位处理:确保在递归返回时正确更新每个节点的值,并传递进位。
- 链表修改:算法直接修改了原始链表。如果要求不修改原链表,需要先复制链表。
八、替代方法(反转链表)
如果不希望使用递归,可以反转链表,然后从头部(原最低位)开始加1和处理进位,最后再反转回来。这种方法的空间复杂度为O(1),但需要编写反转链表的代码。
示例步骤:
- 反转链表,使头节点成为最低位。
- 遍历反转后的链表,加1并处理进位。
- 如果最后还有进位,添加新节点。
- 再次反转链表,恢复顺序。
代码略,但这种方法更节省空间,适合长链表。
总结
理解此题的关键在于模拟从低位到高位的加法过程,并正确处理进位。递归方法代码简洁,但需要注意栈空间;反转链表方法空间高效,但代码稍复杂。根据实际情况选择合适的方法。
算法通俗讲解推荐阅读**
【算法--链表】83.删除排序链表中的重复元素--通俗讲解
【算法--链表】删除排序链表中的重复元素 II--通俗讲解
【算法--链表】86.分割链表--通俗讲解
【算法】92.翻转链表Ⅱ--通俗讲解
【算法--链表】109.有序链表转换二叉搜索树--通俗讲解
【算法--链表】114.二叉树展开为链表--通俗讲解
【算法--链表】116.填充每个节点的下一个右侧节点指针--通俗讲解
【算法--链表】117.填充每个节点的下一个右侧节点指针Ⅱ--通俗讲解
【算法--链表】138.随机链表的复制--通俗讲解
【算法】143.重排链表--通俗讲解
【算法--链表】146.LRU缓存--通俗讲解
【算法--链表】147.对链表进行插入排序--通俗讲解
【算法】【链表】148.排序链表--通俗讲解
【算法】【链表】160.相交链表--通俗讲解
【算法】【链表】203.移除链表元素--通俗讲解
【算法】【链表】206.反转链表--通俗讲解
【算法】234.回文链表--通俗讲解
【算法】【链表】237.删除链表中的节点--通俗讲解
关注公众号,获取更多底层机制/ 算法通俗讲解干货!