题46:最小栈(中等)
设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。
实现 MinStack 类:
MinStack() 初始化堆栈对象。
void push(int val) 将元素val推入堆栈。
void pop() 删除堆栈顶部的元素。
int top() 获取堆栈顶部的元素。
int getMin() 获取堆栈中的最小元素。
示例 1:
输入:
["MinStack","push","push","push","getMin","pop","top","getMin"]
[[],[-2],[0],[-3],[],[],[],[]]
输出:
[null,null,null,null,-3,null,0,-2]
解释:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin(); --> 返回 -3.
minStack.pop();
minStack.top(); --> 返回 0.
minStack.getMin(); --> 返回 -2.
思路:
1.一共两个栈,第一个栈正常存储,其中一个存储当前入栈时的数字,对应的栈的最小值。
2.如果栈里面没有元素,此时插入,则最小值就是插入的值。
3.如果此时存在元素,则插入时,对比要插入的值和栈顶的值,去最小的,就是当前插入的元素对应的最小值
代码:
class MinStack
{
public:
stack<int> x_stack;
stack<int> min_stack;
MinStack()
{
min_stack.push(INT_MAX);
}
void push(int val)
{
x_stack.push(val);
min_stack.push(min(min_stack.top(), val));
}
void pop()
{
x_stack.pop();
min_stack.pop();
}
int top()
{
return x_stack.top();
}
int getMin()
{
return min_stack.top();
}
};
题47:逆波兰表达式求值(中等)
给你一个字符串数组 tokens ,表示一个根据 逆波兰表示法 表示的算术表达式。
逆波兰表达式:
逆波兰表达式是一种后缀表达式,所谓后缀就是指算符写在后面。
平常使用的算式则是一种中缀表达式,如 ( 1 + 2 ) * ( 3 + 4 ) 。
该算式的逆波兰表达式写法为 ( ( 1 2 + ) ( 3 4 + ) * ) 。
请你计算该表达式。返回一个表示表达式值的整数。
注意:
有效的算符为 '+'、'-'、'*' 和 '/' 。
每个操作数(运算对象)都可以是一个整数或者另一个表达式。
两个整数之间的除法总是 向零截断 。
表达式中不含除零运算。
输入是一个根据逆波兰表示法表示的算术表达式。
答案及所有中间计算结果可以用 32 位 整数表示。
示例 1:
输入:tokens = ["2","1","+","3","*"]
输出:9
解释:该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9
示例 2:
输入:tokens = ["4","13","5","/","+"]
输出:6
解释:该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6
示例 3:
输入:tokens = ["10","6","9","3","+","-11","*","/","*","17","+","5","+"]
输出:22
解释:该算式转化为常见的中缀算术表达式为:
((10 * (6 / ((9 + 3) * -11))) + 17) + 5
= ((10 * (6 / (12 * -11))) + 17) + 5
= ((10 * (6 / -132)) + 17) + 5
= ((10 * 0) + 17) + 5
= (0 + 17) + 5
= 17 + 5
= 22
思路:
思路:栈
遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中
代码:
class Solution
{
public:
int evalRPN(vector<string> &tokens)
{
stack<int> stk;
int n = tokens.size();
for (int i = 0; i < n; i++)
{
string &token = tokens[i];
if (isNumber(token))
{
stk.push(atoi(token.c_str()));
}
else
{
int num2 = stk.top();
stk.pop();
int num1 = stk.top();
stk.pop();
switch (token[0])
{
case '+':
stk.push(num1 + num2);
break;
case '-':
stk.push(num1 - num2);
break;
case '*':
stk.push(num1 * num2);
break;
case '/':
stk.push(num1 / num2);
break;
}
}
}
return stk.top();
}
bool isNumber(string &token)
{
return !(token == "+" || token == "-" || token == "*" || token == "/");
}
};
题48:两数相加(中等)
给你两个非空的链表,表示两个非负的整数。它们每位数字都是按照逆序的方式存储的,并且每个节点只能存储一位数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
输入: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] 内
思路:
1.直接遍历两个链表,两个链表中同一位置的数字可以直接相加。
2.如果数字相加为10以下,则直接放入新的链表中
3.如果数字相加大于10,则标记一个进位为carry,
4.如果一个链表的下一个节点为nullptr,则将其值为0,知道两个链表都为空
代码:
class Solution
{
public:
ListNode *addTwoNumbers(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 = n1 + n2 + carry;
if (!head)
{
head = tail = new ListNode(sum % 10);
}
else
{
tail->next = new ListNode(sum % 10);
tail = tail->next;
}
carry = sum / 10;
if (l1)
{
l1 = l1->next;
}
if (l2)
{
l2 = l2->next;
}
}
if (carry > 0)
{
tail->next = new ListNode(carry);
}
return head;
}
};
题49:合并两个有序链表(简单)
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]
示例 2:
输入:l1 = [], l2 = []
输出:[]
示例 3:
输入:l1 = [], l2 = [0]
输出:[0]
*/
/**
* 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) {}
* };
*/
思路:
思路:
当 l1 和 l2 都不是空链表时,判断 l1 和 l2 哪一个链表的头节点的值更小,将较小值的节点添加到结果里,当一个节点被添加到结果里之后,将对应链表中的节点向后移一位。
1.我们设定一个哨兵节点 prehead,这可以在最后让我们比较容易地返回合并后的链表。
我们维护一个 prev 指针,我们需要做的是调整它的 next 指针。
然后,我们重复以下过程,直到 l1 或者 l2 指向了 null
2.如果 l1 当前节点的值小于等于 l2 ,我们就把 l1 当前的节点接在 prev 节点的后面同时将 l1 指针往后移一位。
否则,我们对 l2 做同样的操作。不管我们将哪一个元素接在了后面,我们都需要把 prev 向后移一位。
3.在循环终止的时候, l1 和 l2 至多有一个是非空的。由于输入的两个链表都是有序的,
所以不管哪个链表是非空的,它包含的所有元素都比前面已经合并链表中的所有元素都要大。
这意味着我们只需要简单地将非空链表接在合并链表的后面,并返回合并链表即可。
代码:
class Solution
{
public:
ListNode *mergeTwoLists(ListNode *list1, ListNode *list2)
{
ListNode *preHead = new ListNode(-1);
ListNode *prev = preHead;
while (list1 != nullptr && list2 != nullptr)
{
if (list1->val < list2->val)
{
prev->next = list1;
list1 = list1->next;
}
else
{
prev->next = list2;
list2 = list2->next;
}
prev = prev->next;
}
prev->next = list1 == nullptr ? list2 : list1;
return preHead ->next;
}
};
题50:随机链表的复制(中等)
给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。
构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。
例如,如果原链表中有 X 和 Y 两个节点,其中 X.random --> Y 。那么在复制链表中对应的两个节点 x 和 y ,同样有 x.random --> y 。
返回复制链表的头节点。
用一个由 n 个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index] 表示:
val:一个表示 Node.val 的整数。
random_index:随机指针指向的节点索引(范围从 0 到 n-1);如果不指向任何节点,则为 null 。
你的代码 只 接受原链表的头节点 head 作为传入参数。
输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]
图如下:

思路:
1.由于存在随机指针指向的节点,即当我们拷贝节点的时候,「当前节点的随机指针指向的节点」可能还没创建。
2.所以我们需要创建一个哈希表,用于存储每个节点的创建情况。
3.如果「当前节点的后继节点」和「当前节点的随机指针指向的节点」中存在没有创建的,
则立刻创建,并放入哈希表中。
代码:
class Solution
{
public:
unordered_map<Node *, Node *> cachedNode;
Node *copyRandomList(Node *head)
{
if (head == nullptr)
{
return nullptr;
}
if (!cachedNode.count(head))
{
Node *newNode = new Node(head->val);
cachedNode[head] = newNode;
newNode->next = copyRandomList(head->next);
newNode->random = copyRandomList(head->random);
}
return cachedNode[head];
}
};