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("") = 0depth(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 ,编写一个函数来检验这两棵树是否相同。
如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。