Linked List
21. Merge Two Sorted Lists
typedef ListNode* ListLink;
class Solution {
public:
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
if (list1 == nullptr) return list2;
if (list2 == nullptr) return list1;
// 将头节点较小的链表放在 list1
if (list1->val > list2->val) swap(list1, list2);
// 遍历 list2,按大小插入 list1
auto ls1 = list1, ls2 = list2;
while (ls2 != nullptr) {
if (ls1->next != nullptr and ls2->val > ls1->next->val) {}
else { // 插入节点
ListLink temp = new ListNode(ls2->val, ls1->next);
ls1->next = temp;
ls2 = ls2->next;
}
ls1 = ls1->next;
}
return list1;
}
};
typedef ListNode* ListLink;
class Solution {
public:
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
ListLink res = new ListNode(0);
auto p = res, p1 = list1, p2 = list2;
while (p1 and p2) {
if (p1->val < p2->val) {
p->next = p1;
p1 = p1->next;
}
else {
p->next = p2;
p2 = p2->next;
}
p = p->next;
}
while (p1) {
p->next = p1;
p1 = p1->next;
p = p->next;
}
while (p2) {
p->next = p2;
p2 = p2->next;
p = p->next;
}
return res->next;
}
};
class Solution {
public:
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
if (list1 == nullptr) return list2;
if (list2 == nullptr) return list1;
if (list1->val <= list2->val) {
list1->next = mergeTwoLists(list1->next, list2);
return list1;
}
else {
list2->next = mergeTwoLists(list1, list2->next);
return list2;
}
}
};
题面
有两个链表,其中的节点都按照节点值有序排序,请你将这两个链表合并为一个有序链表。
题解
Method One: 直接法
- 将
head->val
较小的链表作为list1
,放在前面被插入 - 遍历
list2
按照node->val
的大小依次插入list1
- 需特判链表为空的情况
Method Two: 双指针
- 构造一个虚拟头节点
res
,并创建指针p
- 分别为两个链表创建指针
p1
和p2
- 遍历
p1
和p2
,直到有一个走到终点 - 将没有走完的指针走完
Method Three: 递归
- 思想和直接法一样,只是插入的逻辑有变化
- 直接法是始终往一个链表插入
- 递归法是根据当前节点的
val
来判断插入方向
83. Remove Duplicates from Sorted List
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
auto p = head;
while (p != nullptr and p->next != nullptr) {
if (p->val == p->next->val) {
p->next = p->next->next;
}
else p = p->next;
}
return head;
}
};
题面
给你一个有序的链表,请删除其中的重复节点。
题解
Method One: 直接法
- 因为链表是有序的,所以直接遍历链表
- 如果
p->val == p->next->val
则删除p->next
节点 - 否则继续前进
141. Linked List Cycle
class Solution {
public:
bool hasCycle(ListNode *head) {
auto fast = head, slow = head;
while (fast and fast->next) {
fast = fast->next->next;
slow = slow->next;
if (fast == slow) return true;
}
return false;
}
};
class Solution {
public:
bool hasCycle(ListNode *head) {
if (!head) return false;
int cnt = 0;
while (cnt < 10010) {
if (head->next == nullptr) return false;
head = head->next;
cnt ++;
}
return true;
}
};
题面
给你一个链表,请判断链表中是否存在环。
题解
Method One: 快慢指针
- 若链表中存在环,则遍历链表是一个死循环
- 因此我们构造两个快慢指针
fast
和slow
fast
每次前进 2 步,slow
每次前进 1 步- 如果存在环,则
fast
和slow
会相遇
Method Two: 暴力
- 注意到题目的数据量为
1e4
- 因此我们可以直接前进
10010
步,判断是否走到终点
160. Intersection of Two Linked Lists
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
auto p = headA, q = headB;
while (p != q) {
if (!p) p = headB;
else p = p->next;
if (!q) q = headA;
else q = q->next;
}
return p;
}
};
题面
有两个链表 headA
和 headB
,请你找出它们尾部相同的部分。
题解
Method One: 双指针
headA
和headB
长度不一致,但是尾部相同- 则有
headA = preA + tail
,headB = preB + tail
- 即
headA + headB = x + tail
,headB + headA = y + tail
- 因此直接找
headA + headB
和headB + headA
的相同尾部
203. Remove Linked List Elements
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
while (head and head->val == val) head = head->next;
auto p = head;
while (p and p->next) {
if (p->next->val == val) p->next = p->next->next;
else p = p->next;
}
return head;
}
};
题面
给你一个链表 head
和一个整数 val
,请删除链表中 p->val == val
的节点。
题解
Method One: 直接法
- 首先特判头节点的值
head->val
是否等于val
- 然后遍历链表,将
p->val == val
的节点删掉
Stack
20. Valid Parentheses
class Solution {
public:
bool isValid(string s) {
stack<char> stk;
for (auto c : s) {
if (stk.size() == 0) stk.push(c);
else if (stk.top() == '(' and c == ')') stk.pop();
else if (stk.top() == '[' and c == ']') stk.pop();
else if (stk.top() == '{' and c == '}') stk.pop();
else stk.push(c);
}
return stk.size() == 0 ? true : false;
}
};
题面
给定一个只包括 '('
,')'
,'{'
,'}'
,'['
,']'
的字符串 s
,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 每个右括号都有一个对应的相同类型的左括号。
题解
Method One: 直接法
- 遍历字符串,如果栈为空则入栈
- 如果栈不为空,则判断栈头和当前字符是否匹配
- 如果匹配,则栈弹出
- 否则入栈
- 最终判断栈是否为空,如果为空则合法
225. Implement Stack using Queues
class MyStack {
private:
int stk[200];
int head;
public:
MyStack() : head(-1) {}
void push(int x) {
stk[++ head] = x;
}
int pop() {
return stk[head --];
}
int top() {
return stk[head];
}
bool empty() {
return head == -1;
}
};
题面
给定一个只包括 '('
,')'
,'{'
,'}'
,'['
,']'
的字符串 s
,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 每个右括号都有一个对应的相同类型的左括号。
题解
Method One: 数组模拟
- 数据范围
100
,则直接开一个长度足够的数组 - 通过下标
head
来模拟入栈和弹出等
496. Next Greater Element I
class Solution {
public:
vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
stack<int> stk;
int flag[10010];
memset(flag, -1, sizeof(flag));
for (auto i : nums2) {
while (!stk.empty() and stk.top() < i) {
int x = stk.top();
flag[x] = i;
stk.pop();
}
stk.push(i);
}
vector<int> res(nums1.size(), -1);
for (int i = 0, l = nums1.size(); i < l; i ++) {
res[i] = flag[nums1[i]];
}
return res;
}
};
题面
nums1
中数字 x
的 下一个更大元素 是指 x
在 nums2
中对应位置 右侧 的 第一个 比 x
大的元素。
给你两个 没有重复元素 的数组 nums1
和 nums2
,下标从 0 开始计数,其中nums1
是 nums2
的子集。
对于每个 0 <= i < nums1.length
,找出满足 nums1[i] == nums2[j]
的下标 j
,并且在 nums2
确定 nums2[j]
的 下一个更大元素 。如果不存在下一个更大元素,那么本次查询的答案是 -1
。
返回一个长度为 nums1.length
的数组 ans
作为答案,满足 ans[i]
是如上所述的 下一个更大元素 。
题解
Method One: 单调栈
- 我们需要在
nums2
中找到x
右侧比x
大的数字y
- 构造一个单调栈,遍历
nums2
,记录被弹出的元素的值x
和新插入的y
- 这些元素会被弹出,说明
x
的右侧存在比x
大的y
- 最后遍历
nums1
找到每个x
对应的y
注意:需注意数据范围 [0, 1e4]
开足够长的数组和下标的记录。
682. Baseball Game
class Solution {
public:
int calPoints(vector<string>& operations) {
stack<int> stk;
for (auto s : operations) {
if (s == "+") {
int x = stk.top(); stk.pop();
int y = stk.top(); stk.pop();
stk.push(y); stk.push(x); stk.push(x + y);
}
else if (s == "D") stk.push(stk.top() * 2);
else if (s == "C") stk.pop();
else stk.push(std::stoi(s));
}
int sum = 0;
while (!stk.empty()) {
sum += stk.top();
stk.pop();
}
return sum;
}
};
题面
你现在是一场采用特殊赛制棒球比赛的记录员。这场比赛由若干回合组成,过去几回合的得分可能会影响以后几回合的得分。
比赛开始时,记录是空白的。你会得到一个记录操作的字符串列表 ops
,其中 ops[i]
是你需要记录的第 i
项操作,ops
遵循下述规则:
- 整数
x
- 表示本回合新获得分数x
"+"
- 表示本回合新获得的得分是前两次得分的总和。题目数据保证记录此操作时前面总是存在两个有效的分数。"D"
- 表示本回合新获得的得分是前一次得分的两倍。题目数据保证记录此操作时前面总是存在一个有效的分数。"C"
- 表示前一次得分无效,将其从记录中移除。题目数据保证记录此操作时前面总是存在一个有效的分数。
请你返回记录中所有得分的总和。
题解
Method One: 直接法
- 用栈模拟题面的操作
844. Backspace String Compare
class Solution {
public:
bool backspaceCompare(string s, string t) {
stack<char> x, y;
for (auto c : s) {
if (c == '#') {
if (!x.empty()) x.pop();
}
else x.push(c);
}
for (auto c : t) {
if (c == '#') {
if (!y.empty()) y.pop();
}
else y.push(c);
}
return x == y;
}
};
题面
给定 s
和 t
两个字符串,当它们分别被输入到空白的文本编辑器后,如果两者相等,返回 true
。#
代表退格字符。
注意:如果对空文本输入退格字符,文本继续为空。
题解
Method One: 直接法
- 遍历字符串,将其文本记录在栈中
- 判断两个栈是否相等
1021. Remove Outermost Parentheses
class Solution {
public:
string removeOuterParentheses(string s) {
stack<char> stk;
string res;
for (auto c : s) {
if (stk.empty()) stk.push(c);
else {
if (c == '(') { // stk 必定不为空
res += c;
stk.push(c);
}
else {
stk.pop();
if (!stk.empty()) res += c;
}
}
}
return res;
}
};
题面
有效括号字符串为空 ""
、"(" + A + ")"
或 A + B
,其中 A
和 B
都是有效的括号字符串,+
代表字符串的连接。
- 例如,
""
,"()"
,"(())()"
和"(()(()))"
都是有效的括号字符串。
如果有效字符串 s
非空,且不存在将其拆分为 s = A + B
的方法,我们称其为原语(primitive),其中 A
和 B
都是非空有效括号字符串。
给出一个非空有效字符串 s
,考虑将其进行原语化分解,使得:s = P_1 + P_2 + ... + P_k
,其中 P_i
是有效括号字符串原语。
对 s
进行原语化分解,删除分解中每个原语字符串的最外层括号,返回 s
。
题解
Method One: 直接法
- 使用栈来维护括号的匹配关系,遍历字符串
- 如果栈为空则直接入栈
- 如果
c = (
,此时栈不可能为空,入栈,res += c
- 如果
c = )
,栈弹出栈头,此时需判断栈是否为空- 如果为空,则说明已经走到括号的最外面
- 否则,
res += c
1047. Remove All Adjacent Duplicates In String
class Solution {
public:
string removeDuplicates(string s) {
string res;
for (auto c : s) {
if (res.empty()) res.push_back(c);
else {
if (res.back() == c) {
while (!res.empty() and res.back() == c) res.pop_back();
}
else res.push_back(c);
}
}
return res;
}
};
题面
给出由小写字母组成的字符串 S
,重复项删除操作会选择两个相邻且相同的字母,并删除它们。
在 S
上反复执行重复项删除操作,直到无法继续删除。
在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。
题解
Method One: 直接法
- 用
string
模拟栈,按题意操作即可
1475. Final Prices With a Special Discount in a Shop
typedef pair<int, int> pii;
class Solution {
public:
vector<int> finalPrices(vector<int>& prices) {
stack<pii> stk; // 递增单调栈
for (int i = 0, l = prices.size(); i < l; i ++) {
if (!stk.empty() and prices[i] <= stk.top().first) {
while (!stk.empty() and prices[i] <= stk.top().first) {
prices[stk.top().second] -= prices[i];
stk.pop();
}
}
stk.push({prices[i], i});
}
return prices;
}
};
题面
给你一个数组 prices
,其中 prices[i]
是商店里第 i
件商品的价格。
商店里正在进行促销活动,如果你要买第 i
件商品,那么你可以得到与 prices[j]
相等的折扣,其中 j
是满足 j > i
且 prices[j] <= prices[i]
的 最小下标 ,如果没有满足条件的 j
,你将没有任何折扣。
请你返回一个数组,数组中第 i
个元素是折扣后你购买商品 i
最终需要支付的价格。
题解
Method One: 单调栈
- 维护一个递增的单调栈,并使用
pair<value, idx>
记录价格和下标 - 当有元素弹出时,说明遇到了更小的价格,则修改其价格
prices[idx]
1544. Make The String Great
class Solution {
public:
string makeGood(string s) {
string stk;
for (auto c : s) {
if (stk.empty() or abs(stk.back() - c) != 32) stk.push_back(c);
else stk.pop_back();
}
return stk;
}
};
题面
给你一个由大小写英文字母组成的字符串 s
。
一个整理好的字符串中,两个相邻字符 s[i]
和 s[i+1]
,其中 0<= i <= s.length-2
,要满足如下条件:
- 若
s[i]
是小写字符,则s[i+1]
不可以是相同的大写字符。 - 若
s[i]
是大写字符,则s[i+1]
不可以是相同的小写字符。
请你将字符串整理好,每次你都可以从字符串中选出满足上述条件的 两个相邻 字符并删除,直到字符串整理好为止。
请返回整理好的 字符串 。题目保证在给出的约束条件下,测试样例对应的答案是唯一的。
注意: 空字符串也属于整理好的字符串,尽管其中没有任何字符。
题解
Method One: 直接法
- 使用
string
来维护答案 - 遍历
s
时按照题意判断即可
1598. Crawler Log Folder
class Solution {
public:
int minOperations(vector<string>& logs) {
int cnt = 0;
for (auto i : logs) {
if (i == "../") cnt = max(0, -- cnt);
else if (i == "./") continue;
else cnt ++;
}
return cnt;
}
};
题面
每当用户执行变更文件夹操作时,LeetCode 文件系统都会保存一条日志记录。
下面给出对变更操作的说明:
"../"
:移动到当前文件夹的父文件夹。如果已经在主文件夹下,则 继续停留在当前文件夹 。"./"
:继续停留在当前文件夹**。**"x/"
:移动到名为x
的子文件夹中。题目数据 保证总是存在文件夹x
。
给你一个字符串列表 logs
,其中 logs[i]
是用户在 i<sup>th</sup>
步执行的操作。
文件系统启动时位于主文件夹,然后执行 logs
中的操作。
执行完所有变更文件夹操作后,请你找出 返回主文件夹所需的最小步数 。
题解
Method One: 直接法
- 直接按照题面模拟即可
1614. Maximum Nesting Depth of the Parentheses
class Solution {
public:
int maxDepth(string s) {
stack<char> stk;
int res = 0;
for (auto c : s) {
if (c != '(' and c != ')') continue;
if (stk.empty()) stk.push(c);
else {
if (stk.top() == '(' and c == ')') stk.pop();
else stk.push(c);
}
res = max(res, int(stk.size()));
}
return res;
}
};
题面
如果字符串满足以下条件之一,则可以称之为 有效括号字符串(valid parentheses string,可以简写为 VPS):
- 字符串是一个空字符串
""
,或者是一个不为"("
或")"
的单字符。 - 字符串可以写为
AB
(A
与B
字符串连接),其中A
和B
都是 有效括号字符串 。 - 字符串可以写为
(A)
,其中A
是一个 有效括号字符串 。
类似地,可以定义任何有效括号字符串 S
的 嵌套深度 depth(S)
:
depth("") = 0
depth(C) = 0
,其中C
是单个字符的字符串,且该字符不是"("
或者")"
depth(A + B) = max(depth(A), depth(B))
,其中A
和B
都是 有效括号字符串depth("(" + A + ")") = 1 + depth(A)
,其中A
是一个 有效括号字符串
例如:""
、"()()"
、"()(()())"
都是 有效括号字符串(嵌套深度分别为 0、1、2),而 ")("
、"(()"
都不是 有效括号字符串 。
给你一个 有效括号字符串 s
,返回该字符串的 s
嵌套深度 。
题解
Method One: 直接法
- 使用栈维护合法的括号序列
- 每次遍历的时候更新一下最大深度
1700. Number of Students Unable to Eat Lunch
class Solution {
public:
int countStudents(vector<int>& students, vector<int>& sandwiches) {
int cnt[2];
for (auto s : students) cnt[s] ++;
for (auto s : sandwiches) {
if (cnt[s] == 0) break;
else cnt[s] --;
}
return cnt[0] + cnt[1];
}
};
题面
学校的自助午餐提供圆形和方形的三明治,分别用数字 0
和 1
表示。所有学生站在一个队列里,每个学生要么喜欢圆形的要么喜欢方形的。
餐厅里三明治的数量与学生的数量相同。所有三明治都放在一个 栈 里,每一轮:
- 如果队列最前面的学生 喜欢 栈顶的三明治,那么会 拿走它 并离开队列。
- 否则,这名学生会 放弃这个三明治 并回到队列的尾部。
这个过程会一直持续到队列里所有学生都不喜欢栈顶的三明治为止。
给你两个整数数组 students
和 sandwiches
,其中 sandwiches[i]
是栈里面第 i<sup></sup>
个三明治的类型(i = 0
是栈的顶部), students[j]
是初始队列里第 j<sup></sup>
名学生对三明治的喜好(j = 0
是队列的最开始位置)。请你返回无法吃午餐的学生数量。
题解
Method One: 直接法
- 分别记录不同喜好的学生人数
- 遍历三明治列表,只要喜欢当前三明治的人数为 0 则退出
- 返回剩余人数总和
2696. Minimum String Length After Removing Substrings
class Solution {
public:
int minLength(string s) {
string stk;
for (auto c : s) {
if (stk.empty()) stk.push_back(c);
else {
if (c == 'B' and stk.back() == 'A') stk.pop_back();
else if (c == 'D' and stk.back() == 'C') stk.pop_back();
else stk.push_back(c);
}
}
return stk.size();
}
};
题面
给你一个仅由 大写 英文字符组成的字符串 s
。
你可以对此字符串执行一些操作,在每一步操作中,你可以从 s
中删除 任一个 "AB"
或 "CD"
子字符串。
通过执行操作,删除所有 "AB"
和 "CD"
子串,返回可获得的最终字符串的 最小 可能长度。
注意,删除子串后,重新连接出的字符串可能会产生新的 "AB"
或 "CD"
子串。
题解
Method One: 直接法
- 使用
string
模拟栈来维护答案
Queue
387. First Unique Character in a String
class Solution {
public:
int firstUniqChar(string s) {
int ls[200];
for (auto c : s) { ls[c] ++; }
for (int i = 0, l = s.size(); i < l; i ++) {
if (ls[s[i]] == 1) return i;
}
return -1;
}
};
题面
给定一个字符串 s
,找到 它的第一个不重复的字符,并返回它的索引 。如果不存在,则返回 -1
。
题解
Method One: 直接法
- 用一个数组记录每个字符出现的次数
- 遍历字符串,找到第一个只出现一次的字符
933. Number of Recent Calls
class RecentCounter {
private:
queue<int> que;
public:
RecentCounter() {}
int ping(int t) {
que.push(t);
while (que.back() - que.front() > 3000) que.pop();
return que.size();
}
};
class RecentCounter {
private:
vector<int> ls;
int idx;
public:
RecentCounter() : ls(), idx(0) {}
int ping(int t) {
ls.push_back(t);
while (t - ls[idx] > 3000) idx ++;
return ls.size() - idx;
}
};
题面
写一个 RecentCounter
类来计算特定时间范围内最近的请求。
请你实现 RecentCounter
类:
RecentCounter()
初始化计数器,请求数为 0 。int ping(int t)
在时间t
添加一个新请求,其中t
表示以毫秒为单位的某个时间,并返回过去3000
毫秒内发生的所有请求数(包括新请求)。确切地说,返回在[t-3000, t]
内发生的请求数。
保证 每次对 ping
的调用都使用比之前更大的 t
值。
题解
Method One: 直接法
- 用队列维护
ping
的时间 - 每次
ping
操作都先让t
入队,然后将不满足条件的弹出 - 返回队列长度即可
2073. Time Needed to Buy Tickets
题面
有 n
个人前来排队买票,其中第 0
人站在队伍 最前方 ,第 (n - 1)
人站在队伍 最后方 。
给你一个下标从 0 开始的整数数组 tickets
,数组长度为 n
,其中第 i
人想要购买的票数为 tickets[i]
。
每个人买票都需要用掉 恰好 1 秒 。一个人 一次只能买一张票 ,如果需要购买更多票,他必须走到 队尾 重新排队(瞬间 发生,不计时间)。如果一个人没有剩下需要买的票,那他将会 离开 队伍。
返回位于位置 k
(下标从 0 开始)的人完成买票需要的时间(以秒为单位)。
题解
BinaryTree
94. Binary Tree Inorder Traversal
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> res;
subFunction(root, res);
return res;
}
void subFunction(TreeNode* root, vector<int>& res) {
if (root == nullptr) return;
else {
subFunction(root->left, res);
res.push_back(root->val);
subFunction(root->right, res);
}
}
};
题面
给定一个二叉树的根节点 root
,返回 它的 中序 遍历 。
题解
Method One: 递归
- 初始化一个结果数组
- 递归遍历二叉树,并按中根序添加元素
Method Two: 迭代
100. Same Tree
class Solution {
public:
bool isSameTree(TreeNode* p, TreeNode* q) {
if (p == nullptr and q == nullptr) return true;
else if ((p == nullptr and q != nullptr) or (p != nullptr and q == nullptr)) return false;
else {
if (p->val == q->val) return isSameTree(p->left, q->left) and isSameTree(p->right, q->right);
else return false;
}
}
};
题面
给你两棵二叉树的根节点 p
和 q
,编写一个函数来检验这两棵树是否相同。
如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。