笔试算法题
- 链表
- 数组
- 1. 两数之和 【数组】【for 循环】
- 3. 无重复字符的最长字串 【字符串】【滑动窗口】
- 5. 最长回文子串 【数组】【中心扩散】【双指针】
- 26. 删除有序数组中的重复项 【数组】【快慢指针】
- 27. 移除元素 【数组】【快慢指针】
- 28. 找出字符串中第一个匹配项的下标 【字符串】【滑动窗口】【哈希映射】
- 34. 在排序数组中查找元素的第一个和最后一个位置 【数组】【二分法】
- 48. 旋转图像 【矩阵】【反转】
- 54. 螺旋矩阵 【矩阵】【边界修改】
- 59. 螺旋矩阵Ⅱ 【矩阵】【边界修改】
- 76. 最小覆盖字串 【字符串】【滑动窗口】
- 167. 两数之和Ⅱ 【数组】【双指针】
- 187. 重复的 DNA 序列 【字符串】【滑动窗口】【哈希映射】
- 283. 移动零 【数组】【快慢指针】
- 303. 区域和检索 【数组】【前缀和】
- 304. 二维区域和检索 【二维数组】【前缀和】
- 316. 去除重复字母 【字符串】【栈】
- 344. 反转字符串 【字符串】【双指针】
- 370. 区间加法 【数组】【差分】
- 380. 0(1) 时间插入、删除和获取随即元素 【数组】【哈希表】【元素交换】
- 410. 分割数组的最大值 【数组】【二分法】
- 438. 找出字符串中所有字母异位词 【字符串】【滑动窗口】
- 528. 按权重随机选择 【数组】【前缀和】【二分法】
- 567. 字符串的排列 【字符串】【滑动窗口】
- 704. 二分查找 【数组】【二分法】
- 710. 黑名单中的随机数 【数组】【哈希映射】
- 870 优势洗牌 【数组】【优先队列】【双指针】
- 875. 爱吃香蕉的珂珂 【数组】【二分法】
- 1011. 在 D 天内送达包裹的能力 【数组】【二分法】
- 1081. 不同字符的最小子序列 【字符串】【栈】
- 1094. 拼车 【数组】【差分】
- 1109. 航班预定统计 【数组】【差分】
- 二叉树
- 95. 不同的二叉搜索树Ⅱ 【BST】【分解】【后序】
- 98. 验证二叉搜索树 【BST】【遍历】【中序】
- 104. 二叉树的最大深度 【二叉树】【遍历】【前序】
- 105. 从前序与中序遍历序列构造二叉树 【二叉树】【构造】【分解】【前序】
- 106. 从中序与后序遍历序列构造二叉树 【二叉树】【构造】【分解】【前序】
- 114. 二叉树展开为链表 【二叉树】【分解】【后序】
- 116. 填充每个节点的下一个右侧节点指针 【多叉树】【遍历】【前序】
- 222. 完全二叉树的节点个数 【完全二叉树】【分解】
- 226. 翻转二叉树 【二叉树】【遍历】【前序】
- 230. 二叉搜索树中第 K 小的元素 【BST】【遍历】【中序】
- 235. 二叉搜索树的最近公共祖先 【公共祖先】【分解】【后续】
- 236. 二叉树的最近公共祖先 【公共祖先】【分解】【后序】
- 297. 二叉树的序列化与反序列化 【二叉树】【序列化】【分解】【前序】
- 341. 扁平化嵌套列表迭代器 【嵌套数组】【遍历】
- 450. 删除二叉搜索树中的节点 【BST】【遍历】【前序】
- 538. 把二叉搜索树转换为累加树 【BST】【遍历】【中序】
- 543. 二叉树的直径 【二叉树】【分解】【后序】
- 652. 寻找重复的子树 【二叉树】【序列化】【分解】【后序】
- 654. 最大二叉树 【二叉树】【构造】【分解】【前序】
- 700. 二叉搜索树中的搜索 【BST】【遍历】【前序】
- 701. 二叉搜索树中的插入操作 【BST】【遍历】【前序】
- 889. 根据前序和后序遍历构造二叉树 【二叉树】【构造】【分解】【前序】
- 1038. 从二叉搜索树到更大和树 【BST】【遍历】【中序】
- 1644. 二叉树的最近公共祖先Ⅱ 【公共祖先】【分解】【后续】
- 1650. 二叉树的最近公共祖先Ⅲ 【公共祖先】【链表相交】
- 1676. 二叉树的最近公共祖先Ⅳ 【公共祖先】【分解】【后续】
- 图
- 排序
链表
19. 删除链表的倒数第 N 个节点 【链表】【双指针】
题解:
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
// 定义虚拟头节点 dummy
ListNode* dummy = new ListNode(-1);
dummy->next = head;
// 定义双指针 P1 P2
ListNode* p1 = dummy, * p2 = dummy;
// 让 P1 先走 n+1 步
for (int i = 0; i <= n; ++i) {
p2 = p2->next;
}
// 再让 P1 P2 同时走
while (p2 != nullptr) {
p1 = p1->next;
p2 = p2->next;
}
// 此时 P2 为空,P1 指向倒数第 n+1 个节点,方便删除倒数第 n 个节点
p1->next = p1->next->next;
// head 有可能被删除,所以需要虚拟头节点
return dummy->next;
}
};
21. 合并两个有序链表 【链表】【双指针】
题解:
class Solution {
public:
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
// 定义一个虚拟头节点 dummy,p 标记 dummy 链表的节点位置
ListNode* dummy = new ListNode(-1), *p = dummy;
// 定义双指针,分别标记 L1 和 L2 的位置
ListNode* p1 = list1, * p2 = list2;
// 将 L1 和 L2 依次比较,较小的接在 dummy 链表之后
while (p1 != nullptr && p2 != nullptr) {
// 将 L1 节点放在 dummy 之后
if (p1->val < p2->val) {
p->next = p1;
p = p->next;
p1 = p1->next;
} else { // 将 L2 节点放在 dummy 之后
p->next = p2;
p = p->next;
p2 = p2->next;
}
}
// 如果 L1 L2 中有一个为空,把另一个的所有接在 dummy 后面
if (p1 == nullptr) {
p->next = p2;
}
if (p2 == nullptr) {
p->next = p1;
}
// 返回合并后的链表
return dummy->next;
}
};
23. 合并 K 个有序链表 【链表】【多指针】【优先队列】
题解:
class Solution {
public:
struct comp {
bool operator()(ListNode* a, ListNode* b) {
// a > b 才是最小堆,默认是最大堆
return a->val > b->val;
}
};
ListNode* mergeKLists(vector<ListNode*>& lists) {
// 定义 dummy 节点,后接排序结果
ListNode* dummy = new ListNode(-1), * p = dummy;
// 定义优先队列,且为最小堆
priority_queue<ListNode*, vector<ListNode*>, comp> pq;
// 依次取链表,都把第一个节点放进去
for (auto list : lists) {
if (list != nullptr) {
pq.push(list);
}
}
// 如果优先队列不为空,则一直从中取元素
while (!pq.empty()) {
// 这里的 node 是优先队列取出的节点,但也相当于合并两个链表时的指针
// 后续需要通过 node 来添加新的节点到 pq
ListNode* node = pq.top();
pq.pop();
p->next = node;
p = p->next;
// 如果某条链表不为空,继续添加节点进去
if (node->next != nullptr) {
pq.push(node->next);
}
}
// 返回排序后的链表
return dummy->next;
}
};
25. K 为一组反转链表 【链表】【递归】【迭代】
题解:
## 假设原链表为 1 -> 2 -> 3 -> 4 -> 5 -> null, k = 2
## 可以先试着反转区间 [a, b) 间的节点, 其中 b - a = k
## 那么可以得到 null <- 1(a) <- 2(newHead)
## 递归可以得到 null <- 3(a) <- 4(newHead), 将 1 连接上 4
## 剩余的不足 K 个的节点保持不反转
class Solution {
public:
ListNode* reverseKGroup(ListNode* head, int k) {
if (head == nullptr) return nullptr;
// 定义区间 [a, b)
ListNode* a, * b;
a = head, b = head;
for (int i = 0; i < k; ++i) {
// 如果剩余节点不足 k 个, 返回剩余链表头节点
if (b == nullptr) return head;
b = b->next;
}
// 根据 a b 区间反转节点
ListNode* newHead = reverse(a, b);
a->next = reverseKGroup(b, k);
return newHead;
}
// 迭代反转区间 [a, b) 间的节点
ListNode* reverse(ListNode* a, ListNode* b) {
// 定义前置, 当前, 后置指针
ListNode* pre, * cur, * nxt;
pre = nullptr, cur = a;
// 开始反转
while (cur != b) {
nxt = cur->next;
cur->next = pre;
pre = cur;
cur = nxt;
}
// 返回新的头节点
return pre;
}
};
81. 分隔链表 【链表】【双指针】
题解:
class Solution {
public:
ListNode* partition(ListNode* head, int x) {
// 创建两个链表,分别存放大于小于 x 的节点
ListNode* dummy1 = new ListNode(-1), *p1 = dummy1;
ListNode* dummy2 = new ListNode(-1), *p2 = dummy2;
// 为初始链表赋予一个指针 p
ListNode* p = head;
// 开始遍历
while (p != nullptr) {
ListNode* tmp = p;
// 把小于 x 的节点放在 L1
if (p->val < x) {
p = p->next;
p1->next = tmp;
p1 = p1->next;
p1->next = nullptr;
} else { // 把大于 x 的节点放在 L2
p = p->next;
p2->next = tmp;
p2 = p2->next;
p2->next = nullptr;
}
// 【注意】当 L1 或 L2 后加节点时,要将原来的 next 置空,否则会形成环
}
// 将两部分拼接
p1->next = dummy2->next;
return dummy1->next;
}
};
83. 删除排序链表中的重复元素 【链表】【双指针】
题解:
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
// 特殊情况
if (head == nullptr || head->next == nullptr) return head;
// 定义双指针
ListNode* p1 = head, * p2 = head->next;
// 开始迭代
while (p2 != nullptr) {
if (p1->val != p2->val) {
p1->next = p2;
p1 = p1->next;
}
p2 = p2->next;
}
// 把剩下的重复的节点给去除了
p1->next = nullptr;
return head;
}
};
92. 反转链表Ⅱ 【链表】【递归】
题解:
## 首先得实现反转前 n 个节点
## 那么对于 head 的 [2, 4] 个节点可以转化为 head->next->next 的 [0, 2] 即前 3 个节点
class Solution {
public:
ListNode* reverseBetween(ListNode* head, int left, int right) {
if (head == nullptr) return nullptr;
// 如果满足前 K 条件
if (left == 1) {
return reverseK(head, right);
}
// 不满足的话, 逼迫其满足
head->next = reverseBetween(head->next, left - 1, right - 1);
return head;
}
// 记录反转链表的后继节点
ListNode* suc = nullptr;
// 递归反转前 k 个节点
ListNode* reverseK(ListNode* head, int k) {
// 终止条件, 获取新头节点
if (k == 1) {
suc = head->next;
return head;
}
// 递归子任务, 并传递新头节点
ListNode* newHead = reverseK(head->next, k - 1);
// 反转节点
head->next->next = head;
head->next = suc;
return newHead;
}
};
141. 环形链表 【链表】【快慢指针】
题解:
class Solution {
public:
bool hasCycle(ListNode *head) {
// 如果为空链表, 则无环
if (head == nullptr) return false;
// 定义虚拟头节点 dummy
ListNode* dummy = new ListNode(-1);
dummy->next = head;
// 定义快慢指针 P1 P2
ListNode* p1 = dummy, * p2 = dummy;
// P1 每次走一格, P2 每次走两格
while (p2 != nullptr) {
p1 = p1->next;
if (p2->next != nullptr) {
p2 = p2->next->next;
} else {
p2 = p2->next;
}
// P1 P2 相遇, 一定有环
if (p1 == p2) {
return true;
}
}
// 此时 P2 为空, 一定无环
return false;
}
};
142. 环形链表Ⅱ 【链表】【快慢指针】
题解:
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
// 空链表, 则无环
if (head == nullptr) return nullptr;
// 定义虚拟头节点 dummy
ListNode* dummy = new ListNode(-1);
dummy->next = head;
// 定义快慢指针 P1 P2
ListNode* p1 = dummy, * p2 = dummy;
// 先判断有无环, P1 每次走一格, P2 每次走两格
while (p2 != nullptr) {
p1 = p1->next;
if (p2->next != nullptr) {
p2 = p2->next->next;
} else {
p2 = p2->next;
}
// 此时有环
if (p1 == p2) {
break;
}
}
// P2 为空, 则无环
if (p2 == nullptr) {
return nullptr;
} else {
// 接着把 P1 放到起点, P1 P2 同步移动, 相遇位置就是环起点
// P1 P2 在相遇点之间走过了 K, P2 独自从相遇点绕了一圈,环长为 K 的倍数
// 相遇点到入环点的距离为 m, 则 P2 到达入环点要 k-m 格, 从 dummy 到 入环点也需要 k-m 格
p1 = dummy;
while (p1 != p2) {
p1 = p1->next;
p2 = p2->next;
}
return p1;
}
}
};
160. 相交链表 【链表】【双指针】
题解:
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
// 定义双指针 P1 P2
ListNode* p1 = headA, * p2 = headB;
// 先让 P1 P2 同步走, 碰到末尾了重复遍历另一头节点
// 如果相交那么肯定会相遇
while (p1 != p2) {
if (p1 != nullptr) {
p1 = p1->next;
} else {
p1 = headB;
}
if (p2 != nullptr) {
p2 = p2->next;
} else {
p2 = headA;
}
}
// 此处满足 P1 = P2, 可能为节点, 可也能同为 nullptr
return p1;
}
};
206. 反转链表 【链表】【递归】
题解:
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if (head == nullptr) return nullptr;
return reverse(head);
}
// 递归反转链表
ListNode* reverse(ListNode* head) {
// 终止条件, 目的是获取新的头节点
if (head->next == nullptr) {
return head;
}
// 递归, 并传递新的根节点
ListNode* newHead = reverse(head->next);
// 假设递归子任务处理完毕后, 当前节点需要:
head->next->next = head;
// 主要是确保 1 号节点最终指向 nullptr
head->next = nullptr;
return newHead;
}
};
234. 回文链表 【链表】【递归】【双指针】
题解:
class Solution {
public:
// 定义 left 指针, 从左到右遍历
ListNode* left = nullptr;
bool isPalindrome(ListNode* head) {
left = head;
return traverse(head);
}
// 使用递归的后序遍历,从右到左遍历
bool traverse(ListNode* right) {
if (right == nullptr) return true;
bool res = traverse(right->next);
// 后续遍历, 如果满足条件,则对比下一对节点
res = res && (left->val == right->val);
left = left->next;
return res;
}
};
876. 链表的中间节点 【链表】【快慢指针】
题解:
class Solution {
public:
ListNode* middleNode(ListNode* head) {
// 定义虚拟头节点 dummy
ListNode* dummy = new ListNode(-1);
dummy->next = head;
// 定义双指针 P1 P2
ListNode* p1 = dummy, * p2 = dummy;
// P1 每次移动一格, P2 每次移动两格
while (p2 != nullptr) {
p1 = p1->next;
// 因为有 ->next->next 存在, 需要确保 ->next 不为空
if (p2->next != nullptr) {
p2 = p2->next->next;
} else {
p2 = p2->next;
}
}
// P1 此时指向中间节点
return p1;
}
};
数组
1. 两数之和 【数组】【for 循环】
题解:
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
// 朴实无华的两次 for 循环
for (int i = 0; i < nums.size(); ++i) {
for (int j = i + 1; j < nums.size(); ++j) {
if (nums[i] + nums[j] == target) {
return {i, j};
}
}
}
return {-1, -1};
}
};
3. 无重复字符的最长字串 【字符串】【滑动窗口】
题解:
class Solution {
public:
int lengthOfLongestSubstring(string s) {
// 滑动窗口
int left = 0, right = 0;
int res = 0;
// 统计
unordered_map<char, int> window;
// 开始滑动
while (right < s.size()) {
char ch = s[right];
right++;
window[ch]++;
while (window[ch] > 1) {
char ch = s[left];
left++;
window[ch]--;
}
res = max(res, right - left);
}
return res;
}
};
5. 最长回文子串 【数组】【中心扩散】【双指针】
题解:
// 回文串需要从中间某一个元素, 或两个元素从两边扩散判断
// 针对每个元素, 都尝试求下它的回文长度
class Solution {
public:
string longestPalindrome(string s) {
string res = "";
for (int i = 0; i < s.size(); ++i) {
string s1 = palindrome(s, i, i);
string s2 = palindrome(s, i, i + 1);
if (res.size() < s1.size()) {
res = s1;
}
if (res.size() < s2.size()) {
res = s2;
}
}
return res;
}
// 求字符串 s[n], s[n, n+1] 的最大回文子串
string palindrome(string s, int left, int right) {
while (left >= 0 && right < s.size() && s[left] == s[right]) {
left--;
right++;
}
return s.substr(left + 1, right - left - 1);
}
};
26. 删除有序数组中的重复项 【数组】【快慢指针】
题解:
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
if (nums.size() <= 1) return nums.size();
// 定义快慢指针
int p1 = 0, p2 = 1;
while (p2 < nums.size()) {
if (nums[p1] != nums[p2]) {
p1++;
// 相当于 [0, 0, 1, 1] -> [0, 1, 1, 1]
// 碰到一个与 p1 不同的值, 将该值填充直到 p1
nums[p1] = nums[p2];
}
p2++;
}
// 返回的是长度
return p1 + 1;
}
};
27. 移除元素 【数组】【快慢指针】
题解:
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
// 特殊情况
if (nums.size() == 0) return 0;
// 定义双指针
int p1 = 0, p2 = 0;
// 开始迭代
while (p2 < nums.size()) {
if (nums[p2] != val) {
nums[p1] = nums[p2];
p1++;
}
p2++;
}
return p1;
}
};
28. 找出字符串中第一个匹配项的下标 【字符串】【滑动窗口】【哈希映射】
题解:
class Solution {
public:
int strStr(string haystack, string needle) {
int L = needle.size();
int R = 256;
long Q = 13131313;
long RL = 1;
for (int i = 0; i < L - 1; ++i) {
RL = RL * R % Q;
}
// 计算字符串的哈希值
long hashN = 0;
for (int i = 0; i < needle.size(); ++i) {
hashN = (hashN * R + int(needle[i])) % Q;
}
// 开滑
int left = 0, right = 0;
long hashW = 0;
while (right < haystack.size()) {
hashW = (hashW * R % Q + int(haystack[right]) % Q) % Q;
right++;
// 判断是否满足条件
if (right - left == L) {
if (hashW == hashN) {
if (haystack.substr(left, right - left) == needle) {
return left;
}
}
// 缩窗口, 因为 hashW - RL*s[left] 可能为负数, +Q 确保不为负
hashW = (hashW - int(haystack[left]) * RL % Q + Q) % Q;
left++;
}
}
return -1;
}
};
34. 在排序数组中查找元素的第一个和最后一个位置 【数组】【二分法】
题解:
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
int left = find_left(nums, target);
int right = find_right(nums, target);
return {left, right};
}
// 二分查找 - 左边界
int find_left(vector<int>& nums, int target) {
int left = 0, right = nums.size();
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] >= target) {
right = mid;
} else if (nums[mid] < target) {
left = mid + 1;
}
}
if (left >= 0 && left < nums.size() && nums[left] == target) {
return left;
} else {
return -1;
}
}
// 二分查找 - 右边界
int find_right(vector<int>& nums, int target) {
int left = 0, right = nums.size();
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] <= target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid;
}
}
if (left - 1 >= 0 && left - 1 < nums.size() && nums[left - 1] == target) {
return left - 1;
} else {
return -1;
}
}
};
48. 旋转图像 【矩阵】【反转】
题解:
class Solution {
public:
void rotate(vector<vector<int>>& matrix) {
// 矩阵的旋转 -> 矩阵的对角反转 + 矩阵的水平翻转
// 对角反转
int m = matrix.size(), n = matrix[0].size();
for (int i = 0; i < m; ++i) {
for (int j = i + 1; j < n; ++j) {
int tmp = matrix[i][j];
matrix[i][j] = matrix[j][i];
matrix[j][i] = tmp;
}
}
// 水平翻转
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n / 2; ++j) {
// [0 3] [1 2] // [0 2]
int tmp = matrix[i][j];
matrix[i][j] = matrix[i][n - 1 - j];
matrix[i][n - 1 - j] = tmp;
}
}
}
};
54. 螺旋矩阵 【矩阵】【边界修改】
题解:
class Solution {
public:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
// 设置上下左右边界, 用作 for 循环条件, 缩小边界范围
int m = matrix.size(), n = matrix[0].size();
int upper = 0, lower = m - 1, left = 0, right = n - 1;
// 存放结果
vector<int> res;
// 开始遍历数组, 4个方向为一次循环
while (res.size() < m * n) {
if (upper <= lower) {
for (int i = left; i <= right; ++i) {
res.push_back(matrix[upper][i]);
}
upper++;
}
if (left <= right) {
for (int i = upper; i <= lower; ++i) {
res.push_back(matrix[i][right]);
}
right--;
}
if (upper <= lower) {
for (int i = right; i >= left; --i) {
res.push_back(matrix[lower][i]);
}
lower--;
}
if (left <= right) {
for (int i = lower; i >= upper; --i) {
res.push_back(matrix[i][left]);
}
left++;
}
}
return res;
}
};
59. 螺旋矩阵Ⅱ 【矩阵】【边界修改】
题解:
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
// 创建矩阵
vector<vector<int>> res(n, vector<int>(n));
// 已经填充的个数
int num = 1;
int upper = 0, lower = n - 1, left = 0, right = n - 1;
// 开始填充
while (num <= n * n) {
if (upper <= lower) {
for (int i = left; i <= right; ++i) {
res[upper][i] = num++;
}
upper++;
}
if (left <= right) {
for (int i = upper; i <= lower; ++i) {
res[i][right] = num++;
}
right--;
}
if (upper <= lower) {
for (int i = right; i >= left; --i) {
res[lower][i] = num++;
}
lower--;
}
if (left <= right) {
for (int i = lower; i >= upper; --i) {
res[i][left] = num++;
}
left++;
}
}
return res;
}
};
76. 最小覆盖字串 【字符串】【滑动窗口】
题解:
// 从一个子串中找另一个字串, 想到滑动窗口
// 先移动窗口右边界, 寻找符合条件的解
// 再移动窗口左边界, 寻找最优解
class Solution {
public:
string minWindow(string s, string t) {
// 滑动窗口
int left = 0, right = 0;
// 寻求字串的起始, 长度
int start = 0, len = INT_MAX;
// 统计符合条件的字符数
int valid = 0;
// 统计结果, 窗口, 目标
unordered_map<char, int> window, need;
// 给 need 初始化一下
for (auto ch : t) {
need[ch]++;
}
// 开始滑动窗口
while (right < s.size()) {
// 移动右边界
char ch = s[right];
right++;
if (need.count(ch)) {
window[ch]++;
if (window[ch] == need[ch]) {
valid++;
}
}
// 符合条件时移动左边界
while (left <= right && valid == need.size()) {
// 更新最优子串
if (right - left < len) {
start = left;
len = right - left;
}
// 移动左边界
char ch = s[left];
left++;
if (need.count(ch)) {
if (window[ch] == need[ch]) {
valid--;
}
window[ch]--;
}
}
}
if (len == INT_MAX) {
return "";
} else {
return s.substr(start, len);
}
}
};
167. 两数之和Ⅱ 【数组】【双指针】
题解:
class Solution {
public:
vector<int> twoSum(vector<int>& numbers, int target) {
// 定义双指针
int p1 = 0, p2 = numbers.size() - 1;
// 二分查找 0, 1, ..., N-1
while (p1 < p2) {
int sum = numbers[p1] + numbers[p2];
if (sum > target) {
p2--;
} else if (sum < target) {
p1++;
} else {
return {p1 + 1, p2 + 1};
}
}
return {-1, -1};
}
};
187. 重复的 DNA 序列 【字符串】【滑动窗口】【哈希映射】
题解:
class Solution {
public:
vector<string> findRepeatedDnaSequences(string s) {
vector<int> nums(s.size());
for (int i = 0; i < s.size(); ++i) {
switch (s[i]) {
case 'A':
nums[i] = 0;
break;
case 'G':
nums[i] = 1;
break;
case 'C':
nums[i] = 2;
break;
case 'T':
nums[i] = 3;
break;
}
}
// 滑动窗口
int left = 0, right = 0;
// 进制, 数字长度
int R = 4, L = 10, RL = pow(R, L - 1);
int windowHash = 0;
// 已经出现的字符串的哈希值
unordered_set<int> seen;
// 记录重复出现的字符串, 用 set 去重
unordered_set<string> res;
// 开始滑动窗口
while (right < nums.size()) {
windowHash = windowHash * R + nums[right];
right++;
if (right - left == L) {
if (seen.count(windowHash)) {
res.insert(s.substr(left, right - left));
} else {
seen.insert(windowHash);
}
// 缩小窗口, 左边界移动
windowHash = windowHash - nums[left] * RL;
left++;
}
}
return vector<string>(res.begin(), res.end());
}
};
283. 移动零 【数组】【快慢指针】
题解:
class Solution {
public:
void moveZeroes(vector<int>& nums) {
// 定义双指针
int p1 = 0, p2 = 0;
// 开始迭代
while (p2 < nums.size()) {
if (nums[p2] != 0) {
nums[p1] = nums[p2];
p1++;
}
p2++;
}
cout << p1 << endl;
for (int i = p1; i < nums.size(); ++i) {
nums[i] = 0;
}
}
};
303. 区域和检索 【数组】【前缀和】
题解:
class NumArray {
private:
vector<int> nums;
vector<int> preSum;
public:
NumArray(vector<int>& nums) {
// 构造前缀和数组
this->nums = nums;
preSum.resize(nums.size());
preSum[0] = nums[0];
for (int i = 1; i < preSum.size(); ++i) {
preSum[i] = preSum[i - 1] + nums[i];
}
}
int sumRange(int left, int right) {
int sum = preSum[right] - preSum[left] + nums[left];
return sum;
}
};
304. 二维区域和检索 【二维数组】【前缀和】
题解:
class NumMatrix {
private:
vector<vector<int>> matrix;
vector<vector<int>> preSum;
public:
NumMatrix(vector<vector<int>>& matrix) {
this->matrix = matrix;
int m = matrix.size(), n = matrix[0].size();
preSum = vector<vector<int>>(m + 1, vector<int>(n + 1, 0));
for (int i = 1; i < m + 1; ++i) {
for (int j = 1; j < n + 1; ++j) {
preSum[i][j] = preSum[i - 1][j] + preSum[i][j - 1] - preSum[i - 1][j - 1] + matrix[i - 1][j - 1];
}
}
}
int sumRegion(int row1, int col1, int row2, int col2) {
if (matrix.size() == 0) return 0;
int sum = preSum[row2 + 1][col2 + 1] - preSum[row2 + 1][col1] - preSum[row1][col2 + 1] + preSum[row1][col1];
return sum;
}
};
316. 去除重复字母 【字符串】【栈】
题解:
class Solution {
public:
string removeDuplicateLetters(string s) {
// 搞一个栈, 记录放入的不重复元素
stack<char> stk;
// 用一个set 统计字符当前的出现次数
unordered_map<char, int> umap;
// 用一个 set 统计字符有无出现过
unordered_set<char> uset;
for (char c : s) {
umap[c]++;
}
// 开始遍历
for (char c : s) {
// 取一个字符, 在统计 map 里更新
umap[c]--;
// 如果这个字符在 栈里出现过
if (uset.count(c)) continue;
// 没有出现过, 则添加
// 先与上一个元素的字典序比较
while (!stk.empty() && stk.top() > c) {
// 判断后续还有没有这个字符了
if (umap[stk.top()] == 0) {
break;
} else {
// 后续还有, 那就取出来, 之后再添加
uset.erase(stk.top());
stk.pop();
}
}
// 添加新的元素
stk.push(c);
uset.insert(c);
}
// 将栈中元素取出并反转
string res = "";
while (!stk.empty()) {
res = stk.top() + res;
stk.pop();
}
return res;
}
};
344. 反转字符串 【字符串】【双指针】
题解:
class Solution {
public:
void reverseString(vector<char>& s) {
// 定义双指针
int p1 = 0, p2 = s.size() - 1;
while (p1 < p2) {
char tmp = s[p1];
s[p1] = s[p2];
s[p2] = tmp;
p1++;
p2--;
}
}
};
370. 区间加法 【数组】【差分】
题解:
class Solution {
public:
vector<int> getModifiedArray(int length, vector<vector<int>>& updates) {
vector<int> diff(length, 0);
for (auto update : updates) {
// 进入前
diff[update[0]] += update[2];
// 出去时, 判断是否在边界退出
if (update[1] + 1 < length) {
diff[update[1] + 1] -= update[2];
}
}
vector<int> res(length, 0);
res[0] = diff[0];
for (int i = 1; i < length; ++i) {
res[i] = res[i - 1] + diff[i];
}
return res;
}
};
380. 0(1) 时间插入、删除和获取随即元素 【数组】【哈希表】【元素交换】
题解:
// 为了确保随机返回, 需要用数组来实现
// 为了插入删除复杂度 O(1), 需要先找到元素与尾部元素交换
class RandomizedSet {
private:
unordered_map<int, int> umap;
vector<int> nums;
public:
RandomizedSet() {
}
bool insert(int val) {
if (umap.count(val)) {
return false;
} else {
umap[val] = nums.size();
nums.push_back(val);
return true;
}
}
bool remove(int val) {
if (!umap.count(val)) {
return false;
} else {
// 待移除元素 和 最后元素的索引
int s_idx = umap[val], l_idx = nums.size() - 1;
int tmp = nums[s_idx];
nums[s_idx] = nums[l_idx];
nums[l_idx] = tmp;
// 修改最后元素的索引
umap[nums[s_idx]] = s_idx;
nums.pop_back();
// 移除原有元素的索引
umap.erase(val);
return true;
}
}
int getRandom() {
int randIdx = rand() % nums.size();
return nums[randIdx];
}
};
410. 分割数组的最大值 【数组】【二分法】
题目:
// 这题可以等效为货船那题【1011】 , 分成 k 份货物, 使货船 k 次可以运完
class Solution {
public:
int splitArray(vector<int>& nums, int k) {
return find_left(nums, k);
}
// 最大和为 x, 那么分成的份数 k 会随之改变
int func(vector<int>& nums, int x) {
int k = 0;
int sum = 0;
for (int i = 0; i < nums.size(); ++i) {
sum += nums[i];
if (sum <= x) continue;
if (sum > x) {
k++;
sum = nums[i];
}
}
if (sum > 0) k++;
return k;
}
// 二分法, 修改 x, k也会改变, 寻找 k 为固定时的左值
int find_left(vector<int>& nums, int target) {
int max_num = *max_element(nums.begin(), nums.end());
int left = max_num, right = max_num * nums.size();
while (left < right) {
int mid = left + (right - left) / 2;
if (func(nums, mid) > target) {
left = mid + 1;
} else if (func(nums, mid) <= target) {
right = mid;
}
}
return left;
}
};
438. 找出字符串中所有字母异位词 【字符串】【滑动窗口】
题解:
class Solution {
public:
vector<int> findAnagrams(string s, string p) {
// 滑动窗口
int left = 0, right = 0, valid = 0;
// 存放索引数组
vector<int> res;
// 统计
unordered_map<char, int> window, need;
for (auto ch : p) {
need[ch]++;
}
// 开始滑动
while (right < s.size()) {
char ch = s[right];
right++;
if (need.count(ch)) {
window[ch]++;
if (window[ch] == need[ch]) {
valid++;
}
}
// 左边界
while (right - left == p.size()) {
if (valid == need.size()) {
res.push_back(left);
}
// 左移
char ch = s[left];
left++;
if (need.count(ch)) {
if (window[ch] == need[ch]) {
valid--;
}
window[ch]--;
}
}
}
return res;
}
};
528. 按权重随机选择 【数组】【前缀和】【二分法】
题解:
// 给了几个数, 让我随机挑选一个数的下标, 按值分配概率
// 那么可以搞成前缀和数组, 一个随机数随机的落在该前缀和数组上
// 比如 [1, 2, 3] -> [_1, __3, ___6]
// rand(1) = 1, rand(23) = 2, rand(456) = 3
// 下标的寻找靠二分法寻找
class Solution {
private:
// 构建前缀和数组
vector<int> preSum;
public:
Solution(vector<int>& w) {
// 初始化前缀和数组
preSum = vector<int>(w.size() + 1, 0);
for (int i = 1; i <= w.size(); ++i) {
preSum[i] = preSum[i - 1] + w[i - 1];
}
}
int pickIndex() {
// 搞个随机数, [1, preSum[-1]]
int randNum = rand() % preSum[preSum.size() - 1] + 1;
// 把这个数放入前缀和数组, 应该的下标是多少呢
return find_left(preSum, randNum) - 1;
}
// 二分查找
int find_left(vector<int>& nums, int target) {
int left = 0, right = nums.size();
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] >= target) {
right = mid;
}
}
return left;
}
};
567. 字符串的排列 【字符串】【滑动窗口】
题解:
class Solution {
public:
bool checkInclusion(string s1, string s2) {
// 使用哈希表统计出现信息
unordered_map<char, int> window, need;
for (auto ch : s1) {
need[ch]++;
}
// 滑动窗口边界
int left = 0, right = 0;
int valid = 0;
// 开始滑动
while (right < s2.size()) {
char ch = s2[right];
right++;
if (need.count(ch)) {
window[ch]++;
if (window[ch] == need[ch]) {
valid++;
}
}
// 左边界
while (right - left == s1.size()) {
if (valid == need.size()) {
return true;
}
// 左移
char ch = s2[left];
left++;
if (need.count(ch)) {
if (need[ch] == window[ch]) {
valid--;
}
window[ch]--;
}
}
}
return false;
}
};
704. 二分查找 【数组】【二分法】
题解:
class Solution {
public:
int search(vector<int>& nums, int target) {
// 从一个有序的数组中查找元素, 首先想到二分查找
int left = 0, right = nums.size();
// 开始二分
while (left < right) {
int mid = left + (right - left) / 2;
// [left, mid), [mid] [mid + 1, right)
if (nums[mid] > target) {
right = mid;
} else if (nums[mid] < target) {
left = mid + 1;
} else {
return mid;
}
}
return -1;
}
};
710. 黑名单中的随机数 【数组】【哈希映射】
题解:
// 使用一个哈希表存放黑名单中的数
// 如果前 wn 个数, 获取的不是黑名单的数, 那么直接返回
// 如果前 wn 个数, 获取的是黑名单的数, 将这个黑名单数映射到一个 wn 后的白名单的数
class Solution {
private:
int wn;
unordered_map<int, int> umap;
public:
Solution(int n, vector<int>& blacklist) {
// 计算白名单的前 wn 个数
wn = n - blacklist.size();
// 把黑名单记录下来方便查询
for (int num : blacklist) {
umap[num] = 666;
}
int last = n - 1;
for (int num : blacklist) {
// 本身不在前 wn 个, 不用管
if (num >= wn) {
continue;
}
// 如果所处位置在白名单, 则找一个位于最后的白名单的数, 交换
while (umap.count(last)) {
last--;
}
umap[num] = last;
last--;
}
}
int pick() {
int randIdx = rand() % wn;
if (umap.count(randIdx)) {
return umap[randIdx];
} else {
return randIdx;
}
}
};
870 优势洗牌 【数组】【优先队列】【双指针】
题解:
class Solution {
public:
struct comp {
bool operator() (const pair<int, int>& a, const pair<int, int>& b) {
return a.second < b.second;
}
};
vector<int> advantageCount(vector<int>& nums1, vector<int>& nums2) {
int n = nums1.size();
priority_queue<pair<int, int>, vector<pair<int, int>>, comp> pq;
for (int i = 0; i < n; ++i) {
pq.push({i, nums2[i]});
}
sort(nums1.begin(), nums1.end(), [](int a, int b){return a > b;});
vector<int> res(n, 0);
// 双指针
int left = 0, right = n - 1;
while (!pq.empty()) {
auto cur = pq.top();
pq.pop();
cout << cur.second << " ";
if (cur.second < nums1[left]) {
res[cur.first] = nums1[left];
left++;
} else {
res[cur.first] = nums1[right];
right--;
}
}
return res;
}
};
875. 爱吃香蕉的珂珂 【数组】【二分法】
题解:
// 有一堆香蕉要吃, 需要在规定时间内吃完
// 香蕉是恒定的, 吃香蕉的速度 K, 与需要吃的时间 H 成反比, 可以整个函数
// 二分查找来寻找左边界
class Solution {
public:
int minEatingSpeed(vector<int>& piles, int h) {
return find_left(piles, h);
}
// 吃香蕉函数
long func(vector<int>& nums, int K) {
long H = 0;
for (auto num : nums) {
H += num / K;
if (num % K != 0) {
H++;
}
}
return H;
}
// 二分查找
int find_left(vector<int>& piles, int target) {
long left = 1, right = 10e9;
while (left < right) {
int mid = left + (right - left) / 2;
if (func(piles, mid) <= target) {
right = mid;
} else if (func(piles, mid) > target) {
left = mid + 1;
}
}
return int(left);
}
};
1011. 在 D 天内送达包裹的能力 【数组】【二分法】
题解:
// 现在有一些包裹, 这些包裹的总重量固定, 需要 f(x) 天运送, 每次运用的重量 x 可变
// 现在求 x 为多少时, f(x) 为 5, 明显用二分法
// 先构造函数
class Solution {
public:
int shipWithinDays(vector<int>& weights, int days) {
return find_left(weights, days);
}
// 求运送量为 x 时, 天数 f(x) 为多少
int func(vector<int>& weights, int x) {
int day = 0;
int sum = 0;
for (int i = 0; i < weights.size(); ++i) {
sum += weights[i];
if (sum <= x) continue;
if (sum > x) {
day++;
sum = weights[i];
}
}
if (sum > 0) day++;
return day;
}
// 二分搜素
int find_left(vector<int>& weights, int target) {
int max_num = *max_element(weights.begin(), weights.end());
int left = max_num, right = 500 * weights.size();
while (left < right) {
int mid = left + (right - left) / 2;
if (func(weights, mid) > target) {
left = mid + 1;
} else if (func(weights, mid) <= target) {
right = mid;
}
}
return left;
}
};
1081. 不同字符的最小子序列 【字符串】【栈】
题解:
class Solution {
public:
string smallestSubsequence(string s) {
// 使用 map 存放剩余字符的出现字数
// 使用 set 记录当前字符有无出现过
unordered_map<char, int> umap;
unordered_set<char> uset;
for (char c : s) {
umap[c]++;
}
// 定义栈用于存放符合条件的字符
stack<char> stk;
// 开始遍历
for (char c : s) {
umap[c]--;
// 添加过这个字符, 直接跳过
if (uset.count(c)) {
continue;
}
// 第一次遇到, 判断是否加进去
// 如果有字典序更大的数被添加了已经, 找到更小字典序的数
while (!stk.empty() && stk.top() > c) {
// 如过后续没有多余的 top 数
if (umap[stk.top()] == 0) {
break;
}
uset.erase(stk.top());
stk.pop();
}
// 终于此时我的字典序最大了, 直接添加
stk.push(c);
uset.insert(c);
}
// 获取字符串
string res = "";
while (!stk.empty()) {
res = stk.top() + res;
stk.pop();
}
return res;
}
};
1094. 拼车 【数组】【差分】
题解:
class Solution {
public:
bool carPooling(vector<vector<int>>& trips, int capacity) {
// 区间和 -> 差分数组 -> 判断元素是否大于 capacity
int start = INT_MAX, end = 0;
for (auto trip : trips) {
if (trip[1] < start) {
start = trip[1];
}
if (trip[2] > end) {
end = trip[2];
}
}
cout << start << " " << end << endl;
// 差分数组
vector<int> diff(end - start + 1);
vector<int> res(end - start + 1);
// 因为到站了下车, 所以应该在 diff[to] 处减, 而不是 diff[to+1]
for (auto trip : trips) {
diff[trip[1] - start] += trip[0];
if (trip[2] - start < end - start + 1) {
diff[trip[2] - start] -= trip[0];
}
}
// 恢复原数组
res[0] = diff[0];
if (res[0] > capacity) return false;
for (int i = 1; i < end - start + 1; ++i) {
res[i] = res[i - 1] + diff[i];
if (res[i] > capacity) return false;
}
for (int i = 0; i < end - start + 1; ++i) {
cout << res[i] << endl;
}
return true;
}
};
1109. 航班预定统计 【数组】【差分】
题解:
class Solution {
public:
vector<int> corpFlightBookings(vector<vector<int>>& bookings, int n) {
// 区间加法问题, 一眼差分数组
vector<int> diff(n);
vector<int> res(n);
for (auto book : bookings) {
diff[book[0] - 1] += book[2];
if (book[1] < n) {
diff[book[1]] -= book[2];
}
}
// 根据差分数组恢复原数组
res[0] = diff[0];
for (int i = 1; i < n; ++i) {
res[i] = res[i - 1] + diff[i];
}
return res;
}
};
二叉树
95. 不同的二叉搜索树Ⅱ 【BST】【分解】【后序】
题解:
class Solution {
public:
vector<TreeNode*> generateTrees(int n) {
return build(1, n);
}
// 构建二叉树, 返回所有种类的排列树
vector<TreeNode*> build(int left, int right) {
vector<TreeNode*> res;
if (left > right) {
res.push_back(nullptr);
return res;
}
if (left == right) {
TreeNode* root = new TreeNode(left);
res.push_back(root);
return res;
}
for (int i = left; i <= right; ++i) {
vector<TreeNode*> left_nodes = build(left, i - 1);
vector<TreeNode*> right_nodes = build(i + 1, right);
for (auto left_node : left_nodes) {
for (auto right_node : right_nodes) {
// 构建根节点
TreeNode* root = new TreeNode(i);
root->left = left_node;
root->right = right_node;
// 把构建好的子树作为一类, 放入 res
res.push_back(root);
}
}
}
return res;
}
};
98. 验证二叉搜索树 【BST】【遍历】【中序】
题解:
class Solution {
private:
long pre_max = LONG_MIN;
public:
bool isValidBST(TreeNode* root) {
return traverse(root);
}
bool traverse(TreeNode* root) {
// 终止
if (root == nullptr) return true;
bool left = traverse(root->left);
if (root->val <= pre_max) return false;
pre_max = root->val;
bool right = traverse(root->right);
return left && right;
}
};
104. 二叉树的最大深度 【二叉树】【遍历】【前序】
题解:
class Solution {
private:
int depth = 0, res = 0;
public:
int maxDepth(TreeNode* root) {
traverse(root);
return res;
}
void traverse(TreeNode* root) {
// 终止条件
if (root == nullptr) return;
// 前序位置
depth++;
if (res < depth) {
res = depth;
}
traverse(root->left);
traverse(root->right);
// 后序位置
depth--;
}
};
105. 从前序与中序遍历序列构造二叉树 【二叉树】【构造】【分解】【前序】
题解:
class Solution {
private:
unordered_map<int, int> umap;
public:
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
int n = preorder.size();
for (int i = 0; i < inorder.size(); ++i) {
umap[inorder[i]] = i;
}
return build(preorder, inorder, 0, n - 1, 0, n - 1);
}
TreeNode* build(vector<int>& pre, vector<int> in, int pre_l, int pre_r, int in_l, int in_r) {
if (pre_l > pre_r) return nullptr;
// 确定左右节点的个数
int pre_root = pre[pre_l];
int in_idx = umap[pre_root];
int left_num = in_idx - in_l;
// 构建根节点
TreeNode* node = new TreeNode(pre_root);
// [pre_l] [pre_l+1, pre_l+left_num] [pre_l+left_num+1, pre_r]
// [in_l, in_l+left_num] [in_idx] [in_idx+1, in_r]
node->left = build(pre, in, pre_l + 1, pre_l + left_num, in_l, in_l + left_num);
node->right = build(pre, in, pre_l + left_num + 1, pre_r, in_idx + 1, in_r);
return node;
}
};
106. 从中序与后序遍历序列构造二叉树 【二叉树】【构造】【分解】【前序】
题解:
class Solution {
private:
unordered_map<int, int> umap;
public:
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
int n = inorder.size();
for (int i = 0; i < inorder.size(); ++i) {
umap[inorder[i]] = i;
}
return build(inorder, postorder, 0, n - 1, 0, n - 1);
}
// 构建二叉树
// 中 [左, !中, 右]
// 后 [左, 右, !中]
TreeNode* build(vector<int>& in, vector<int>& post, int in_l, int in_r, int post_l, int post_r) {
if (post_l > post_r) return nullptr;
// 先定位, 划分数组为两部分
int post_root = post[post_r];
int in_idx = umap[post_root];
int left_num = in_idx - in_l;
// 构造根节点
TreeNode* node = new TreeNode(post_root);
// [in_l, in_idx-1] [in_idx] [in_idx+1, in_r]
// [post_l, post_l+left_num-1] [post_l+left_num, post_r-1] [post_r]
node->left = build(in, post, in_l, in_idx - 1, post_l, post_l + left_num - 1);
node->right = build(in, post, in_idx + 1, in_r, post_l + left_num, post_r - 1);
return node;
}
};
114. 二叉树展开为链表 【二叉树】【分解】【后序】
题解:
class Solution {
public:
void flatten(TreeNode* root) {
if (root == nullptr) return;
flatten(root->left);
flatten(root->right);
// 后序
TreeNode* left = root->left;
TreeNode* right = root->right;
root->left = nullptr;
root->right = left;
TreeNode* cur = root;
while (cur->right != nullptr) {
cur = cur->right;
}
cur->right = right;
}
};
116. 填充每个节点的下一个右侧节点指针 【多叉树】【遍历】【前序】
题解:
class Solution {
public:
Node* connect(Node* root) {
if (root == nullptr) return nullptr;
traverse(root->left, root->right);
return root;
}
void traverse(Node* root1, Node* root2) {
// 终止
if (root1 == nullptr) return;
// 进入
root1->next = root2;
traverse(root1->left, root1->right);
traverse(root1->right, root2->left);
traverse(root2->left, root2->right);
}
};
222. 完全二叉树的节点个数 【完全二叉树】【分解】
题解:
class Solution {
public:
int countNodes(TreeNode* root) {
TreeNode* left = root, * right = root;
int h_left = 0, h_right = 0;
while (left != nullptr) {
left = left->left;
h_left++;
}
while (right != nullptr) {
right = right->right;
h_right++;
}
if (h_left == h_right) {
return pow(2, h_left) - 1;
}
int left_num = countNodes(root->left);
int right_num = countNodes(root->right);
return 1 + left_num + right_num;
}
};
226. 翻转二叉树 【二叉树】【遍历】【前序】
题解:
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
traverse(root);
return root;
}
void traverse(TreeNode* root) {
// 终止
if (root == nullptr) return;
// 进入
TreeNode* tmp = root->left;
root->left = root->right;
root->right = tmp;
traverse(root->left);
traverse(root->right);
}
};
230. 二叉搜索树中第 K 小的元素 【BST】【遍历】【中序】
题解:
class Solution {
private:
int k = 0;
int cur = 0;
int res = 0;
public:
int kthSmallest(TreeNode* root, int k) {
this->k = k;
traverse(root);
return res;
}
void traverse(TreeNode* root) {
if (root == nullptr) return;
traverse(root->left);
cur++;
if (cur == k) {
res = root->val;
return;
}
traverse(root->right);
}
};
235. 二叉搜索树的最近公共祖先 【公共祖先】【分解】【后续】
题解:
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
int val1 = min(p->val, q->val);
int val2 = max(p->val, q->val);
return find(root, val1, val2);
}
TreeNode* find(TreeNode* root, int val1, int val2) {
if (root == nullptr) return nullptr;
if (root->val > val2) {
return find(root->left, val1, val2);
}
if (root->val < val1) {
return find(root->right, val1, val2);
}
if (root->val >= val1 && root->val <= val2) {
return root;
}
return root;
}
};
236. 二叉树的最近公共祖先 【公共祖先】【分解】【后序】
题解:
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
return find(root, p->val, q->val);
}
// 遍历查找节点
TreeNode* find(TreeNode* root, int val1, int val2) {
if (root == nullptr) return nullptr;
if (root->val == val1 || root->val == val2) {
return root;
}
TreeNode* left = find(root->left, val1, val2);
TreeNode* right = find(root->right, val1, val2);
// 后序位置, 判断是否左右子树均有目标值
if (left != nullptr && right != nullptr) {
return root;
}
return left != nullptr ? left : right;
}
};
297. 二叉树的序列化与反序列化 【二叉树】【序列化】【分解】【前序】
题解:
class Codec {
private:
// 将序列化数据放在数组中, 方便操作
vector<string> strs;
public:
// Encodes a tree to a single string.
string serialize(TreeNode* root) {
// 碰到空节点的情况
if (root == nullptr) return "#";
string left = serialize(root->left);
string right = serialize(root->right);
string cur = left + "," + right + "," + to_string(root->val);
return cur;
}
// Decodes your encoded data to tree.
TreeNode* deserialize(string data) {
int pos = 0;
while ((pos = data.find(",")) != data.npos) {
strs.push_back(data.substr(0, pos));
data.erase(0, pos + 1);
}
strs.push_back(data);
return build();
}
// 构造二叉树
TreeNode* build() {
if (strs.empty()) return nullptr;
// 找到根节点
string s = strs.back();
strs.pop_back();
if (s == "#") return nullptr;
TreeNode* node = new TreeNode(stoi(s));
node->right = build();
node->left = build();
return node;
}
};
341. 扁平化嵌套列表迭代器 【嵌套数组】【遍历】
题解:
class NestedIterator {
private:
vector<int> res;
vector<int>::iterator it;
// 遍历函数
void traverse(vector<NestedInteger>& nestedList) {
for (int i = 0; i < nestedList.size(); ++i) {
if (nestedList[i].isInteger()) {
res.push_back(nestedList[i].getInteger());
} else {
traverse(nestedList[i].getList());
}
}
}
public:
NestedIterator(vector<NestedInteger> &nestedList) {
traverse(nestedList);
it = res.begin();
}
int next() {
return *it++;
}
bool hasNext() {
return it != res.end();
}
};
450. 删除二叉搜索树中的节点 【BST】【遍历】【前序】
题解:
class Solution {
private:
int key;
int bak; // 删除交换后的节点后, 用于恢复原始的 key
public:
TreeNode* deleteNode(TreeNode* root, int key) {
this->key = key;
this->bak = key;
return traverse(root);
}
// 先通过遍历找到需要删除的节点
// 然后将其删除
TreeNode* traverse(TreeNode* root) {
if (root == nullptr) return nullptr;
if (root->val == key) {
if (root->left == nullptr && root->right == nullptr) {
return nullptr;
} else if (root->left == nullptr){
return root->right;
} else if (root->right == nullptr) {
return root->left;
} else {
TreeNode* mini = getMin(root->right);
key = mini->val;
root->right = traverse(root->right);
key = bak;
mini->left = root->left;
mini->right = root->right;
root = mini;
}
} else if (root->val > key) {
root->left = traverse(root->left);
} else {
root->right = traverse(root->right);
}
return root;
}
// 获取以该节点开始的最小节点
TreeNode* getMin(TreeNode* root) {
while (root->left != nullptr) {
root = root->left;
}
return root;
}
};
538. 把二叉搜索树转换为累加树 【BST】【遍历】【中序】
题解:
class Solution {
private:
int sum = 0;
public:
TreeNode* convertBST(TreeNode* root) {
traverse(root);
return root;
}
// 遍历 BST
void traverse(TreeNode* root) {
if (root == nullptr) return;
traverse(root->right);
sum += root->val;
root->val = sum;
traverse(root->left);
}
};
543. 二叉树的直径 【二叉树】【分解】【后序】
题解:
class Solution {
private:
int maxDiam = 0;
public:
int diameterOfBinaryTree(TreeNode* root) {
maxDepth(root);
return maxDiam;
}
int maxDepth(TreeNode* root) {
if (root == nullptr) return 0;
int left = maxDepth(root->left);
int right = maxDepth(root->right);
// 后续位置
int curDepth = max(left, right) + 1;
int diam = left + right;
maxDiam = max(maxDiam, diam);
return curDepth;
}
};
652. 寻找重复的子树 【二叉树】【序列化】【分解】【后序】
题解:
class Solution {
private:
// 记录目前出现过的字符串
unordered_map<string, int> umap;
// 记录最终的结果
vector<TreeNode*> res;
public:
vector<TreeNode*> findDuplicateSubtrees(TreeNode* root) {
serialize(root);
return res;
}
string serialize(TreeNode* root) {
if (root == nullptr) return "#";
string left = serialize(root->left);
string right = serialize(root->right);
string cur = left + "," + right + "," + to_string(root->val);
if (umap.count(cur)) {
if (umap[cur] == 1) {
res.push_back(root);
}
umap[cur]++;
} else {
umap[cur]++;
}
return cur;
}
};
654. 最大二叉树 【二叉树】【构造】【分解】【前序】
题解:
class Solution {
public:
TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
return traverse(nums, 0, nums.size());
}
// 遍历构建数
TreeNode* traverse(vector<int>& nums, int left, int right) {
if (left >= right) return nullptr;
// 先构建根节点
auto max_it = max_element(nums.begin() + left, nums.begin() + right);
int max_idx = (max_it - nums.begin());
TreeNode* node = new TreeNode(*max_it);
// 构建左子树 [left, mid) [mid] [mid + 1, right)
if (left < max_idx) {
node->left = traverse(nums, left, max_idx);
}
// 构建右子树
if (max_idx + 1 < right) {
node->right = traverse(nums, max_idx + 1, right);
}
return node;
}
};
700. 二叉搜索树中的搜索 【BST】【遍历】【前序】
题解:
class Solution {
private:
int val;
TreeNode* res = nullptr;
public:
TreeNode* searchBST(TreeNode* root, int val) {
this->val = val;
traverse(root);
return res;
}
// 遍历, 搜索
void traverse(TreeNode* root) {
if (root == nullptr) return;
if (root->val == val) {
res = root;
return;
}
if (root->val > val) {
traverse(root->left);
}
if (root->val < val) {
traverse(root->right);
}
}
};
701. 二叉搜索树中的插入操作 【BST】【遍历】【前序】
题解:
class Solution {
private:
int val;
public:
TreeNode* insertIntoBST(TreeNode* root, int val) {
if (root == nullptr) return new TreeNode(val);
this->val = val;
traverse(root);
return root;
}
// 先找到位置(叶子节点), 再插入
TreeNode* traverse(TreeNode* root) {
// 找到合适的叶子节点
if (root == nullptr) {
return new TreeNode(val);
}
if (root->val > val) {
root->left = traverse(root->left);
} else {
root->right = traverse(root->right);
}
return root;
}
};
889. 根据前序和后序遍历构造二叉树 【二叉树】【构造】【分解】【前序】
题解:
class Solution {
private:
unordered_map<int, int> umap;
public:
TreeNode* constructFromPrePost(vector<int>& preorder, vector<int>& postorder) {
int n = preorder.size();
for (int i = 0; i < n; ++i) {
umap[postorder[i]] = i;
}
return build(preorder, postorder, 0, n - 1,0 , n - 1);
}
// 构造二叉树
// 前 【中1, 【中2, 左, 右】, 右】
// 后 【【左, 右, 中2】, 右, 中1】
// 根据 中2 判断左半部分节点的个数
TreeNode* build(vector<int>& pre, vector<int>& post, int pre_l, int pre_r, int post_l, int post_r) {
if (pre_l == pre_r) return new TreeNode(pre[pre_l]);
if (pre_l > pre_r) return nullptr;
// 先确定左半节点个数
int pre_root1 = pre[pre_l];
int pre_root2 = pre[pre_l + 1];
int post_idx2 = umap[pre_root2];
int left_num = post_idx2 - post_l + 1;
// 开始构造
TreeNode* node = new TreeNode(pre_root1);
// [pre_l] [pre_l+1, pre_l+left_num] [pre_l+left_num+1, pre_r]
// [post_l, post_l+left_num-1] [post_l+left_num, post_r-1] [post_r]
node->left = build(pre, post, pre_l + 1, pre_l + left_num, post_l, post_l + left_num - 1);
node->right = build(pre, post, pre_l + left_num + 1, pre_r, post_l + left_num, post_r - 1);
return node;
}
};
1038. 从二叉搜索树到更大和树 【BST】【遍历】【中序】
题解:
class Solution {
private:
int sum = 0;
public:
TreeNode* bstToGst(TreeNode* root) {
traverse(root);
return root;
}
// 遍历 BST
void traverse(TreeNode* root) {
if (root == nullptr) return;
traverse(root->right);
sum += root->val;
root->val = sum;
traverse(root->left);
}
};
1644. 二叉树的最近公共祖先Ⅱ 【公共祖先】【分解】【后续】
题解:
class Solution {
private:
bool findP = false, findQ = false;
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
TreeNode* res = find(root, p->val, q->val);
if (findP && findQ) {
return res;
} else {
return nullptr;
}
}
// 寻找子节点有 val1 val2 的父节点
TreeNode* find(TreeNode* root, int val1, int val2) {
if (root == nullptr) return nullptr;
TreeNode* left = find(root->left, val1, val2);
TreeNode* right = find(root->right, val1, val2);
// 后序位置判断, 确保所有节点都有遍历到
if (left != nullptr && right != nullptr) {
return root;
}
// 判断节点是不是目标值
if (root->val == val1 || root->val == val2) {
if (root->val == val1) findP = true;
if (root->val == val2) findQ = true;
return root;
}
return left != nullptr ? left : right;
}
};
1650. 二叉树的最近公共祖先Ⅲ 【公共祖先】【链表相交】
题解:
class Solution {
public:
Node* lowestCommonAncestor(Node* p, Node * q) {
// 由于存在父节点指针, 因此可以视为链表相交问题
Node* a = p, * b = q;
while (a != b) {
if (a == nullptr) {
a = q;
} else {
a = a->parent;
}
if (b == nullptr) {
b = p;
} else {
b = b->parent;
}
}
return a;
}
};
1676. 二叉树的最近公共祖先Ⅳ 【公共祖先】【分解】【后续】
题解:
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, vector<TreeNode*> &nodes) {
unordered_set<int> uset;
for (auto node : nodes) {
uset.insert(node->val);
}
return find(root, uset);
}
// 分解
TreeNode* find(TreeNode* root, unordered_set<int>& uset) {
if (root == nullptr) return nullptr;
// 当前节点的值在 uset 中
if (uset.count(root->val)) return root;
TreeNode* left = find(root->left, uset);
TreeNode* right = find(root->right, uset);
// 判断
if (left != nullptr && right != nullptr) {
return root;
}
return left != nullptr ? left : right;
}
};
图
207. 📙课程表【图】【环判断】【DFS】【邻接表】
class Solution {
private:
unordered_map<int, vector<int>> graph; // 邻接表
vector<int> visited; // 记录遍历过的节点
vector<int> onPath; // 记录当前路径上的节点
bool hasCycle = false; // 记录有无环
public:
bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
// 初始化邻接表
for (int i = 0; i < prerequisites.size(); ++i) {
graph[prerequisites[i][1]].push_back(prerequisites[i][0]);
}
// 遍历数组初始化
visited = vector<int>(numCourses, false);
onPath = vector<int>(numCourses, false);
// 开始逐个访问节点, 判断有无环 DFS
for (int i = 0; i < numCourses; ++i) {
traverse(i);
}
return !hasCycle;
}
// 从节点 val 开始遍历, 将访问过的节点置为 true
void traverse(int val) {
// 如果遍历的路径上已经存在 val, 则标记有环
if (onPath[val]) hasCycle = true;
// 如果有环, 或者碰到过之前已经遍历完的节点, 直接返回
if (hasCycle || visited[val]) return;
// 遇到新节点, 更新状态
visited[val] = true;
onPath[val] = true;
for (int num : graph[val]) {
traverse(num);
}
// 离开时, 当前节点状态更新
onPath[val] = false;
}
};
207. 📙课程表【图】【环判断】【BFS】【邻接表】
class Solution {
private:
unordered_map<int, vector<int>> graph; // 邻接表
vector<int> inDegree; // 入度矩阵
queue<int> que; // 队列, 用于 BFS
public:
bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
// 初始化邻接表
inDegree.resize(numCourses);
for (int i = 0; i < prerequisites.size(); ++i) {
inDegree[prerequisites[i][0]]++;
graph[prerequisites[i][1]].push_back(prerequisites[i][0]);
}
// 队列, 用于 BFS, 将入度为 0 的节点入队
for (int i = 0; i < numCourses; ++i) {
if (inDegree[i] == 0) {
que.push(i);
}
}
int count = 0; // 记录多少门课上过了
// BFS
while (!que.empty()) {
int val = que.front(); que.pop();
count++;
// 更新所有关联课程的入度
for (auto num : graph[val]) {
inDegree[num]--;
// 如果更新后的节点入度为 0, 则假如队列
if (inDegree[num] == 0) {
que.push(num);
}
}
}
if (count == numCourses) return true;
return false;
}
};
210. 📙课程表Ⅱ【图】【环判断】【拓扑排序】【DFS】【邻接表】
class Solution {
private:
unordered_map<int, vector<int>> graph; // 邻接表
vector<int> visited; // 记录遍历过的节点
vector<int> onPath; // 记录当前路径上的节点
bool hasCycle = false; // 记录有无环
vector<int> postorder; // 记录后序遍历的结果
vector<int> res; // 存放最终结果
public:
vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) {
// 初始化邻接表
for (int i = 0; i < prerequisites.size(); ++i) {
graph[prerequisites[i][1]].push_back(prerequisites[i][0]);
}
// 遍历数组初始化
visited = vector<int>(numCourses, false);
onPath = vector<int>(numCourses, false);
// 开始逐个访问节点, 判断有无环 DFS
for (int i = 0; i < numCourses; ++i) {
traverse(i);
}
if (hasCycle) return {};
reverse(postorder.begin(), postorder.end());
res = vector<int>(postorder.begin(), postorder.begin() + numCourses);
return res;
}
// 从节点 val 开始遍历, 将访问过的节点置为 true
void traverse(int val) {
// 如果遍历的路径上已经存在 val, 则标记有环
if (onPath[val]) hasCycle = true;
// 如果有环, 或者碰到过之前已经遍历完的节点, 直接返回
if (hasCycle || visited[val]) return;
// 遇到新节点, 更新状态
visited[val] = true;
onPath[val] = true;
for (int num : graph[val]) {
traverse(num);
}
// 记录后序遍历的结果, 即为拓扑排序的结果
postorder.push_back(val);
// 离开时, 当前节点状态更新
onPath[val] = false;
}
};
210. 📙课程表Ⅱ【图】【环判断】【拓扑排序】【BFS】【邻接表】
class Solution {
private:
unordered_map<int, vector<int>> graph; // 邻接表
vector<int> inDegree; // 入度矩阵
queue<int> que; // 队列, 用于 BFS
vector<int> res; // 拓扑排序结果
public:
vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) {
// 初始化邻接表
inDegree.resize(numCourses);
for (int i = 0; i < prerequisites.size(); ++i) {
inDegree[prerequisites[i][0]]++;
graph[prerequisites[i][1]].push_back(prerequisites[i][0]);
}
// 队列, 用于 BFS, 将入度为 0 的节点入队
for (int i = 0; i < numCourses; ++i) {
if (inDegree[i] == 0) {
que.push(i);
}
}
int count = 0; // 记录多少门课上过了
// BFS
while (!que.empty()) {
int val = que.front(); que.pop();
res.push_back(val);
count++;
// 更新所有关联课程的入度
for (auto num : graph[val]) {
inDegree[num]--;
// 如果更新后的节点入度为 0, 则假如队列
if (inDegree[num] == 0) {
que.push(num);
}
}
}
if (count == numCourses) return res;
return {};
}
};
277. 📙搜索名人【图】【交互】【邻接矩阵】
class Solution {
private:
queue<int> que; // 整个队列, 存放候选名人
public:
int findCelebrity(int n) {
for (int i = 0; i < n; ++i) {
que.push(i);
}
// 一直排除, 直到只剩下一个候选人
while (que.size() > 1) {
int cand = que.front(); que.pop();
int other = que.front(); que.pop();
if (knows(cand, other) || !knows(other, cand)) {
que.push(other);
} else {
que.push(cand);
}
}
// 最后只剩下一个人
int cand = que.front(); que.pop();
for (int other = 0; other < n; ++other) {
if (cand == other) continue;
if (knows(cand, other) || !knows(other, cand)) {
return -1;
}
}
return cand;
}
};
785. 📙判断二分图【图】【二分图】【DFS】【邻接表】
class Solution {
private:
bool isBit = true; // 是否为二分图
int n; // 节点个数
vector<vector<int>> graph; // 邻接表
vector<bool> color; // flase, true 代表两种颜色
vector<bool> visited; // 记录节点是否被访问过
public:
bool isBipartite(vector<vector<int>>& graph) {
int n = graph.size();
for (auto num : graph) {
this->graph.push_back(num);
}
color.resize(n);
visited.resize(n);
// DFS
for (int i = 0; i < n; ++i) {
if (!visited[i]) {
traverse(i);
}
}
return isBit;
}
// DFS
void traverse(int val) {
// 判断当前是否为二分图
if (!isBit) return;
// 进入节点
visited[val] = true;
// 访问相邻节点并染色
for (int num : graph[val]) {
if (!visited[num]) {
color[num] = !color[val];
// 继续遍历
traverse(num);
} else {
if (color[num] == color[val]) {
isBit = false;
return;
}
}
}
}
};
785. 📙判断二分图【图】【二分图】【BFS】【邻接表】
class Solution {
private:
bool isBit = true; // 是否为二分图
int n; // 节点个数
vector<vector<int>> graph; // 邻接表
vector<bool> color; // flase, true 代表两种颜色
vector<bool> visited; // 记录节点是否被访问过
queue<int> que; // 队列
public:
bool isBipartite(vector<vector<int>>& graph) {
int n = graph.size();
for (auto num : graph) {
this->graph.push_back(num);
}
color.resize(n);
visited.resize(n);
// BFS
for (int i = 0; i < n; ++i) {
if (!visited[i]) {
bfs(i);
}
}
return isBit;
}
// BFS
void bfs(int val) {
visited[val] = true;
que.push(val);
while (!que.empty() && isBit) {
int num = que.front(); que.pop();
// 放入相邻的节点
for (auto data : graph[num]) {
// 如果这个节点没有遇到过
if (!visited[data]) {
color[data] = !color[num];
visited[data] = true;
que.push(data);
} else { // 如果遇到过
if (color[data] == color[num]) {
isBit = false;
return;
}
}
}
}
}
};
797. 📙所有可能的路径【图】【路径】【DFS】【邻接表】
class Solution {
private:
unordered_map<int, vector<int>> graph; // 邻接表
int n = 0; // 节点数
vector<int> path; // 单条路径
vector<vector<int>> res; // 所有路径
public:
// 给了邻接表, 求路径
vector<vector<int>> allPathsSourceTarget(vector<vector<int>>& graph) {
// 初始化邻接表
for (int i = 0; i < graph.size(); ++i) {
this->graph[i] = graph[i];
}
this->n = graph.size();
traverse(0);
return res;
}
// DFS
void traverse(int val) {
// 进入节点
path.push_back(val);
// 判断是否退出
if (val == n - 1) {
res.push_back(path);
}
// 访问下一节点
for (auto num : graph[val]) {
traverse(num);
}
// 出去节点
path.pop_back();
}
};
886. 📙可能的二分法【图】【二分图】【DFS】【邻接表】
class Solution {
private:
bool isBit = true; // 是否是二分图
unordered_map<int, vector<int>> graph; // 邻接表
vector<bool> visited; // 节点是否遍历过
vector<bool> color; // 节点是否染色过
public:
bool possibleBipartition(int n, vector<vector<int>>& dislikes) {
// 邻接表
for (auto dislike : dislikes) {
this->graph[dislike[0]].push_back(dislike[1]);
this->graph[dislike[1]].push_back(dislike[0]);
}
visited.resize(n + 1);
color.resize(n + 1);
// DFS, 每个节点都开始一遍
for (int i = 1; i <= n; ++i) {
if (!visited[i]) {
dfs(i);
}
}
return isBit;
}
void dfs(int val) {
// 终止
if (!isBit) return;
// 进入节点
visited[val] = true;
// 开始染色下一层
for (auto num : graph[val]) {
// 对于没有访问过的节点, 染色
if (!visited[num]) {
color[num] = !color[val];
// 继续遍历
dfs(num);
} else {
// 判断是否为二分图
if (color[num] == color[val]) {
isBit = false;
return;
}
}
}
}
};
排序
215. 数组中的第 K 个最大元素 【快速排序】【分解】【先序】【三指针】
题解:
class Solution {
private:
vector<int> data;
int target;
int res;
public:
int findKthLargest(vector<int>& nums, int k) {
int n = nums.size();
target = n - k;
data.resize(n);
for (int i = 0; i < n; ++i) {
data[i] = nums[i];
}
quickSort(0, n - 1);
return res;
}
void quickSort(int left, int right) {
if (left > right) return;
if (left == right) {
if (left == target) {
res = data[target];
return;
}
}
vector<int> p = part(left, right);
if (p[0] + 1 <= target && p[1] - 1 >= target) {
res = data[target];
return;
}
quickSort(left, p[0]);
quickSort(p[1], right);
}
vector<int> part(int left, int right) {
int pivot_idx = rand() % (right - left + 1) + left;
int pivot = data[pivot_idx];
swap(left, pivot_idx);
int curp = left + 1, leftp = left, rightp = right + 1;
while (curp < rightp) {
if (data[curp] < pivot) {
leftp++;
swap(curp, leftp);
curp++;
} else if (data[curp] > pivot) {
rightp--;
swap(curp, rightp);
} else {
curp++;
}
}
swap(left, leftp);
return {leftp - 1, rightp};
}
void swap(int a, int b) {
int tmp = data[a];
data[a] = data[b];
data[b] = tmp;
}
};
315. 计算右侧小于当前元素的个数 【归并排序】【分解】【双指针】
题解:
class Solution {
private:
// 原始数组及下标
vector<pair<int, int>> data;
// 统计大于当前数的次数
vector<int> count;
// 结果
vector<int> res;
public:
vector<int> countSmaller(vector<int>& nums) {
int n = nums.size();
data.resize(n);
count.resize(n);
res.resize(n);
for (int i = 0; i < n; ++i) {
data[i] = make_pair(nums[i], i);
}
mergeSort(0, n - 1);
return count;
}
// 归并排序
void mergeSort(int left, int right) {
if (left >= right) return;
int mid = left + (right - left) / 2;
mergeSort(left, mid);
mergeSort(mid + 1, right);
merge(left, mid, right);
}
// 合并函数
void merge(int left, int mid, int right) {
int len = right - left + 1;
vector<pair<int, int>> bak(len, make_pair(0, 0));
int p1 = left, p2 = mid + 1, p = 0;
while (p1 <= mid && p2 <= right) {
if (data[p1].first <= data[p2].first) {
count[data[p1].second] += p2 - mid - 1;
bak[p++] = data[p1++];
} else {
bak[p++] = data[p2++];
}
}
while (p1 <= mid) {
count[data[p1].second] += p2 - mid - 1;
bak[p++] = data[p1++];
}
while (p2 <= right) {
bak[p++] = data[p2++];
}
for (int i = 0; i < bak.size(); ++i) {
data[left + i] = bak[i];
}
}
};
327. 区间和的个数 【归并排序】【前缀和】【分解】【双指针】【滑动窗口】
题解:
class Solution {
private:
vector<long> preSum;
int count = 0;
long lower, upper;
public:
int countRangeSum(vector<int>& nums, int lower, int upper) {
int n = nums.size();
this->lower = lower;
this->upper = upper;
preSum.resize(n + 1);
for (int i = 0; i < n; ++i) {
preSum[i + 1] = preSum[i] + nums[i];
}
mergeSort(0, n);
return count;
}
void mergeSort(int left, int right) {
if (left >= right) return;
// 先找到一个中点
int mid = left + (right - left) / 2;
// 让子任务先排好序 [left, mid] [mid+1, right]
mergeSort(left, mid);
mergeSort(mid + 1, right);
// 子任务弄好后, 将他俩合并
merge(left, mid, right);
}
// 将两个有序数组排序, 双指针
void merge(int left, int mid, int right) {
// 双指针
int p1 = left, p2 = mid + 1, p = 0;
// 核心逻辑
int start = mid + 1, end = mid + 1;
for (int i = left; i <= mid; ++i) {
for (int j = start; j <= right; ++j) {
if (long(preSum[j]) - long(preSum[i]) < long(lower)) {
start++;
} else {
break;
}
}
for (int k = end; k <= right; ++k) {
if (long(preSum[k]) - long(preSum[i]) <= long(upper)) {
end++;
} else {
break;
}
}
count += end - start;
}
vector<long> bak(right - left + 1, 0);
while (p1 <= mid && p2 <= right) {
if (preSum[p1] < preSum[p2]) {
bak[p++] = preSum[p1++];
} else {
bak[p++] = preSum[p2++];
}
}
while (p1 <= mid) {
bak[p++] = preSum[p1++];
}
while (p2 <= right) {
bak[p++] = preSum[p2++];
}
for (int i = 0; i < bak.size(); ++i) {
preSum[left + i] = bak[i];
}
}
};
493. 翻转对 【归并排序】【分解】【双指针】
题解:
class Solution {
private:
vector<int> data;
int count = 0;
public:
int reversePairs(vector<int>& nums) {
int n = nums.size();
data.resize(n);
for (int i = 0; i < n; ++i) {
data[i] = nums[i];
}
mergeSort(0, n - 1);
return count;
}
// 归并排序
void mergeSort(int left, int right) {
if (left >= right) return;
int mid = left + (right - left) / 2;
mergeSort(left, mid);
mergeSort(mid + 1, right);
merge(left, mid, right);
}
// 将两个有序数组排序, 双指针
void merge(int left, int mid, int right) {
// 双指针
int p1 = left, p2 = mid + 1, p = 0;
int end = mid + 1;
for (int i = left; i <= mid; ++i) {
for (int j = end; j <= right; ++j) {
if (long(data[i]) > 2 * long(data[j])) {
end++;
} else {
break;
}
}
count += (end - (mid + 1));
}
vector<int> bak(right - left + 1, 0);
while (p1 <= mid && p2 <= right) {
if (data[p1] < data[p2]) {
bak[p++] = data[p1++];
} else {
bak[p++] = data[p2++];
}
}
while (p1 <= mid) {
bak[p++] = data[p1++];
}
while (p2 <= right) {
bak[p++] = data[p2++];
}
for (int i = 0; i < bak.size(); ++i) {
data[left + i] = bak[i];
}
}
};
912. 排序数组 【归并排序】【分解】【双指针】
题解:
class Solution {
private:
vector<int> data;
public:
vector<int> sortArray(vector<int>& nums) {
int n = nums.size();
data.resize(n);
for (int i = 0; i < n; ++i) {
data[i] = nums[i];
}
mergeSort(0, n - 1);
return data;
}
void mergeSort(int left, int right) {
if (left >= right) return;
// 先找到一个中点
int mid = left + (right - left) / 2;
// 让子任务先排好序 [left, mid] [mid+1, right]
mergeSort(left, mid);
mergeSort(mid + 1, right);
// 子任务弄好后, 将他俩合并
merge(left, mid, right);
}
// 将两个有序数组排序, 双指针
void merge(int left, int mid, int right) {
// 双指针
int p1 = left, p2 = mid + 1, p = 0;
vector<int> bak(right - left + 1, 0);
while (p1 <= mid && p2 <= right) {
if (data[p1] < data[p2]) {
bak[p++] = data[p1++];
} else {
bak[p++] = data[p2++];
}
}
while (p1 <= mid) {
bak[p++] = data[p1++];
}
while (p2 <= right) {
bak[p++] = data[p2++];
}
for (int i = 0; i < bak.size(); ++i) {
data[left + i] = bak[i];
}
}
};
912. 排序数组 【快速排序】【分解】【先序】【三指针】
题解:
class Solution {
private:
vector<int> data;
public:
vector<int> sortArray(vector<int>& nums) {
int n = nums.size();
data.resize(n);
for (int i = 0; i < n; ++i) {
data[i] = nums[i];
}
quickSort(0, n - 1);
return data;
}
void quickSort(int left, int right) {
if (left >= right) return;
vector<int> p = part(left, right);
quickSort(left, p[0]);
quickSort(p[1], right);
}
vector<int> part(int left, int right) {
// [0, right - left] -> [left, right]
int pivot_idx = rand() % (right - left + 1) + left;
int pivot = data[pivot_idx];
swap(left, pivot_idx);
// leftp cur rightp
// [pivot] [left+1, ..., right-1, right]
int curp = left + 1, leftp = left, rightp = right + 1;
while (curp < rightp) {
// 把 <= pivot 的值都放左边
if (data[curp] < pivot) {
leftp++;
swap(leftp, curp);
curp++;
} else if (data[curp] == pivot) {
curp++;
} else {
// 这里怕交换后的数, 依然大于 pivot
rightp--;
swap(curp, rightp);
}
}
swap(leftp, left);
return {leftp - 1, rightp};
}
void swap(int a, int b) {
int tmp = data[a];
data[a] = data[b];
data[b] = tmp;
}
};