系列目录
88.合并两个有序数组
52.螺旋数组
567.字符串的排列
643.子数组最大平均数
150.逆波兰表达式
61.旋转链表
160.相交链表
83.删除排序链表中的重复元素
389.找不同
1491.去掉最低工资和最高工资后的工资平均值
896.单调序列
206.反转链表
92.反转链表II
141.环形链表
142.环型链表
21.合并两个有序列表
24.两辆交换链表中的节点
876.链表的中间节点
143. 重排链表
2.两数相加
445.两数相加II
目录
- 系列目录
- 2. 两数相加
- 445. 两数相加 II
- 栈
- 调用STL库
- 数组模拟栈
- 链表模拟栈
- 运行时栈(Runtime Stack)[^1]
2. 两数相加
🌟链表
原题链接
C++
若未特殊标明,以下题解均写用C++
/**
* Definition for singly-linked list.
* 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* addTwoNumbers(ListNode* l1, ListNode* l2) {
// 定义一个 tail 让其移动
// 这里head 充当一个 💂
ListNode *head = nullptr, *tail = nullptr;
int carry = 0;
// l1 或 l2 存在
while (l1 || l2 || carry) {
// 这里不能写成——要进行一个 数据类型的转化
// int sum = carry + l1->val + l2->val;
int n1 = l1 ? l1->val : 0;
int n2 = l2 ? l2->val : 0;
int sum = carry + n1 + n2;
// head 不存在,初始化新链表
if (!head) {
// 小数点向前挪一位,只取个位,进位(十位)通过 carry记录
head = tail = new ListNode(sum % 10);
} else {
tail->next = new ListNode(sum % 10);
// 记得更新 tail
tail = tail->next;
}
// 取整得到 carry 进位位
carry = sum / 10;
if (l1) l1 = l1->next;
if (l2) l2 = l2->next;
}
// // l1 和 l2 都遍历完了 还有多余的进位
// if (carry > 0)
// tail->next = new ListNode(carry);
// 这两行可以并在while中
return head;
}
};
445. 两数相加 II
🌟 (反转)链表+递归+栈
原题链接
思路
有了上一题的启发,我们可以先将逆序相加的两个链表,反转过来相加,再反转回去并返回
C++
若未特殊标明,以下题解均写用C++
方法一 反转链表+递归
/**
* Definition for singly-linked list.
* 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 {
// class类 默认 private
//private:
ListNode* reverseList(ListNode* head) {
// pre-preserve cur-current nxt-next
ListNode *pre = nullptr, *cur = head;
while (cur) {
ListNode *nxt = cur->next;
cur->next = pre;
pre = cur;
cur = nxt;
}
return pre;
}
ListNode *addTwo(ListNode* l1, ListNode* l2) {
ListNode *head = nullptr, *tail = nullptr;
int carry = 0;
while (l1 || l2) {
int n1 = l1 ? l1->val : 0;
int n2 = l2 ? l2->val : 0;
int sum = carry + n1 + n2;
// 初始化新链表
if (!head) {
head = tail = new ListNode(sum % 10);
} else {
tail->next = new ListNode(sum % 10);
tail = tail->next;
}
// 更新进位位
carry = sum / 10;
// 别忘了更新 l1、l2
if (l1) l1 = l1->next;
if (l2) l2 = l2->next;
}
// 溢出的进位位处理
if (carry > 0)
tail->next = new ListNode(carry);
return head;
}
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
l1 = reverseList(l1);
l2 = reverseList(l2);
auto l3 = addTwo(l1, l2);
// 将l1 和 l2 反转过来相加;再将所得的结果反转回去
return reverseList(l3);
}
};
写法二
/**
* Definition for singly-linked list.
* 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 {
private:
ListNode* reverseList(ListNode* head) {
// pre-preserve cur-current nxt-next
ListNode *pre = nullptr, *cur = head;
while (cur) {
ListNode *nxt = cur->next;
cur->next = pre;
pre = cur;
cur = nxt;
}
return pre;
}
ListNode* addTwo(ListNode* l1, ListNode* l2) {
int carry = 0;
ListNode *tail = nullptr, *head = nullptr;
while (l1 || l2 || carry) {
int n1 = l1 ? l1->val : 0;
int n2 = l2 ? l2->val : 0;
int sum = n1 + n2 + carry;
if (!head) {
head = tail = new ListNode(sum % 10);
} else {
tail->next = new ListNode(sum % 10);
// 别忘了更新 tail
tail = tail->next;
}
carry = sum / 10;
if (l1) l1 = l1->next;
if (l2) l2 = l2->next;
} // while_end
return head;
}
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
l1 = reverseList(l1);
l2 = reverseList(l2);
auto l3 = addTwo(l1, l2);
return reverseList(l3);
}
};
思路:
借助栈逆序处理所有数位
方法二 栈
/**
* Definition for singly-linked list.
* 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* addTwoNumbers(ListNode* l1, ListNode* l2) {
stack<int> s1, s2;
while (l1) {
s1.push(l1->val);
l1 = l1->next;
}
while (l2) {
s2.push(l2->val);
l2 = l2->next;
}
int carry = 0;
// 用于 反转链表
ListNode *res = nullptr;
while (!s1.empty() or !s2.empty() or carry) {
int a = s1.empty() ? 0 : s1.top();
int b = s2.empty() ? 0 : s2.top();
// 逻辑删除栈顶元素
if (!s1.empty()) s1.pop();
if (!s2.empty()) s2.pop();
int sum = a + b + carry;
carry = sum / 10;
sum %= 10;
// 可用 auto 代替 ListNode*
auto node = new ListNode(sum);
// 反转链表
node->next = res;
res = node;
}
return res;
}
};
栈
- 遵循后进先出(LIFO, Last In First Out)的原则
- 主要用于存储数据元素,这些数据元素被称为栈项(Stack Items)
基本操作
- Push(推入):将一个新元素添加到栈的顶部
- Pop(弹出):从栈的顶部移除一个元素,并返回该元素的值 如果栈为空,则弹出操作可能会失败或引发错误
- Top(顶部):返回栈顶元素的值,但不移除它
- IsEmpty(是否为空):检查栈是否为空
- Size(大小):返回栈中元素的数量
实现
在C++中,通常使用标准模板库(STL)中的stack
容器来实现栈
或用数组或链表来模拟栈
应用
栈在计算机科学中有广泛的应用,包括函数调用(调用栈)、表达式求值(如后缀表达式)、括号匹配、深度优先搜索(DFS)等 在操作系统中,栈也用于保存局部变量和返回地址 在C++中,当函数被调用时,其参数和局部变量通常被存储在调用栈上
调用STL库
调用STL库
#include <iostream>
#include <stack>
using namespace std;
int main() {
stack<int> myStack; // 创建一个空的int类型栈
// Push元素到栈中
myStack.push(1);
myStack.push(2);
myStack.push(3);
// 显示栈顶元素
cout << "栈顶元素: " << myStack.top() << endl; // 输出: 3
// 弹出并显示栈顶元素
cout << "弹出的元素: " << myStack.pop() << endl; // 输出: 3
// 显示栈的大小
cout << "栈的大小: " << myStack.size() << endl; // 输出: 2
// 检查栈是否为空
if (myStack.empty()) {
cout << "栈为空" << endl;
} else {
cout << "栈不为空" << endl; // 输出: 栈不为空
}
return 0;
}
数组模拟栈
数组模拟
#include <iostream>
#include <cassert>
using namespace std;
class Stack {
private:
int* arr;
int peek;
int capacity;
public:
// 构造函数
Stack(int size) {
capacity = size;
arr = new int[capacity];
peek = -1; // 栈顶初始化为-1,表示栈为空
}
// 析构函数
// 释放 new 运算符分配的资源,对象的生命周期结束后自动被调用
~Stack() {
delete[] arr;
}
// 检查栈是否为空
bool isEmpty() {
return peek == -1;
}
// 检查栈是否已满
bool isFull() {
return peek == capacity - 1;
}
// 入栈操作
void push(int value) {
if (isFull()) {
cerr << "Stack is full, cannot push element.\n";
return;
}
arr[peek ++] = value; // 先移动栈顶指针,再赋值
}
// 出栈操作
int pop() {
if (isEmpty()) {
cerr << "Stack is empty, cannot pop element.\n";
return -1; // 或者可以抛出异常
}
return arr[peek --]; // 先返回栈顶元素的值,再移动栈顶指针
}
// 查看栈顶元素
int top() {
if (isEmpty()) {
cerr << "Stack is empty, cannot top element.\n";
return -1; // 或者可以抛出异常
}
return arr[peek];
}
// 获取栈的大小
int size() {
return peek + 1;
}
};
int main() {
Stack s(5); // 创建一个容量为5的栈
s.push(1);
s.push(2);
s.push(3);
cout << "peek element: " << s.top() << endl; // 输出: 3
cout << "Stack size: " << s.size() << endl; // 输出: 3
cout << "Popped element: " << s.pop() << endl; // 输出: 3
return 0;
}
链表模拟栈
链表模拟
#include <iostream>
using namespace std;
// 链表节点类
template <typename T>
class ListNode {
public:
T data;
ListNode* next;
ListNode(T x) : data(x), next(nullptr) {}
};
// 栈类
template <typename T>
class Stack {
private:
ListNode<T>* top; // 栈顶指针
public:
Stack() : top(nullptr) {} // 构造函数
// 判断栈是否为空
bool isEmpty() const {
return top == nullptr;
}
// 向栈顶添加元素
void push(T x) {
ListNode<T>* newNode = new ListNode<T>(x);
newNode->next = top;
top = newNode;
}
// 从栈顶移除元素
T pop() {
if (isEmpty()) {
throw runtime_error("Stack is empty!");
}
ListNode<T>* temp = top;
T data = temp->data;
top = top->next;
delete temp;
return data;
}
// 返回栈顶元素但不移除
T topElement() const {
if (isEmpty()) {
throw runtime_error("Stack is empty!");
}
return top->data;
}
// 析构函数,释放所有节点
~Stack() {
while (!isEmpty()) {
pop();
}
}
};
int main() {
Stack<int> s;
s.push(1);
s.push(2);
s.push(3);
cout << "Top element: " << s.topElement() << endl;
s.pop();
cout << "Top element after pop: " << s.topElement() << endl;
return 0;
}
运行时栈(Runtime Stack)1
程序在执行过程中用于存储函数调用的相关信息和局部变量等数据的内存区域
- 栈的基本结构和功能
- 栈的结构:栈是一种后进先出(LIFO)的数据结构,程序栈实际上是一块内存区域,满足先进后出的原则 从栈底到栈顶,地址由高变低,所以新加入栈的以及新开辟的空间的地址都是较小的
- 功能:运行时栈主要用来支持函数调用的执行
这包括保存函数的返回地址、传递函数参数、存储局部变量等
- 栈帧(Stack Frame)
- 定义:栈帧是为单个过程(函数)分配的那一小部分栈
它保存了函数调用的上下文信息 - 内容:
- 局部变量:包括函数内部定义的变量,以及作为参数传递的变量
- 返回地址:当函数执行完毕后,需要返回到调用该函数的地方继续执行,这个地址就被保存在栈帧中
- 其他信息:可能还包括一些其他的状态信息,如保存的寄存器值等
- 运行时栈的操作
- 分配空间:当函数调用发生时,会为其在栈上分配一个新的栈帧,用于存储该函数的局部变量等信息
- 释放空间:当函数执行完毕并返回时,其栈帧会被销毁,释放之前分配的空间 这个过程是通过栈指针
%rsp
的移动来实现的
- 寄存器与栈的关系
- 帧指针(Frame Pointer):
%rbp
:基指针寄存器(Base Pointer Register),保存当前栈帧开始的位置 大多数信息访问都是相对于帧指针进行的 - 栈指针(Stack Pointer):
%rsp
:栈指针寄存器(Stack Pointer Register),始终指向栈顶 通过减小栈指针的值来分配空间,增加栈指针来释放空间
- 运行时栈与程序执行
- 函数调用:当函数被调用时,会将其参数、局部变量等信息压入栈中,并保存返回地址
并跳转到被调用函数的起始地址执行 - 函数返回:当函数执行完毕时,会弹出栈顶的返回地址,并跳转到该地址继续执行
同时,该函数的栈帧也会被销毁,释放其占用的空间
- 栈的大小限制
- 运行时栈的大小是有限的,如果递归调用过深或局部变量过多,可能导致栈溢出(Stack Overflow)
- 总结
运行时栈是程序执行过程中非常重要的内存区域,它支持函数调用的执行,并通过栈帧来保存函数调用的上下文信息
参考自 《深入理解计算机系统》CSAPP ↩︎