文章目录
堆栈
压栈出栈序列
- 问题:输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。
- 解法:采用模拟方法,为序列
pushV构造一个栈,为序列popV构造一个队列。不断压栈,直到遇到(位于队首的)当前需要出栈的元素;然后不断出栈,直到需要继续压栈。
#include <vector>
class Solution {
public:
bool IsPopOrder(vector<int>& pushV, vector<int>& popV) {
int fst = 0;
int len = pushV.size();
vector<int> stack;
int i = 0;
while (i != len) {
if (pushV[i] != popV[fst]) {
// (压栈后的)栈顶不是预期的出栈队首
stack.push_back(pushV[i]); // 那么实际压栈
} else {
fst++; // 否则将它压栈并出栈(stack 不变,出栈队列移除队首)
while (!stack.empty() && stack.back() == popV[fst]) {
stack.pop_back(); // 然后不断出栈
fst++;
}
}
i++; // 继续压栈
}
return stack.empty(); // 为空,那么模拟顺利
}
};
最大堆
- 问题:请你实现一个堆 (大根堆)。操作:
push x:将 x 加入堆。保证x为int型整数。不输出任何内容。top:输出堆顶元素。若堆为空,输出 “empty” (不含引号)。pop:输出堆顶元素,且弹出堆顶。若堆为空,输出 “empty” (不含引号)。 - 解法:直接使用
<algorithm>里的接口(默认:最大堆),可以自定义comp运算,但要注意它被用于堆的从上到下的重建扫描(最大堆需要定义严格小于)
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
int main() {
int n;
cin >> n;
auto lt = [](int x, int y) -> bool {
return x < y;
};
vector<int> maxheap;
make_heap(maxheap.begin(), maxheap.end(), lt); // 首先初始化堆(默认最大堆)
for(int i=0;i<n;i++){
string str;
int val;
cin >> str;
if(str == "push"){
cin >> val;
maxheap.push_back(val); // 堆尾插入,向上到根的比较
push_heap(maxheap.begin(), maxheap.end(), lt);
}
else{
if(maxheap.empty())
cout << "empty" << endl;
else{
if(str == "top")
cout << maxheap[0] << endl;
else{
cout << maxheap[0] << endl;
pop_heap(maxheap.begin(), maxheap.end(), lt);
maxheap.pop_back(); // 堆首移到堆尾,从上到下的扫描
}
}
}
}
}
柱状图中的最大矩形
- 问题:给定
n个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为1。求在该柱状图中,能够勾勒出来的矩形的最大面积。 - 解法:对于每个柱子,用动态规划分别求出左边、右边更高柱子的数量;然后计算以该柱子的高度,所能找到的最大矩形;最后扫描一遍获得全局的最大矩形。注意,左邻居比自己低,那么状态转移时立即的;但是如果左邻居比自己高,那么该左邻居的左侧连续更高柱子,仅仅是本柱子的子集,需要继续和更左边的柱子做比较,直到遇到比自己更低的。这里隐含了一个贪心策略:如果一个(固定的宽度区间)矩形没有完全覆盖其宽度上的任意柱子,那么扩大该矩形的高度,必然可以获得更大面积的矩形;因此最大面积的矩形必然完全覆盖了某个柱子。
- 另一种算法:基于单调递增栈(从栈底到栈顶的值不断递增)构造
Left[i]和Right[i],其结构为stack<pair<int, int>>(索引 + 高度),在将(i, heights[i])入栈时把栈里更高的那些都出栈(剩下的栈顶(j, heights[j])就是最近的比自己低的),其栈顶记录了比本柱子更低的最近的左右柱子索引。
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
int len = heights.size();
vector<int> Left(len, 0);
vector<int> Right(len, 0);
for (int i = 1; i < len; i++) {
int j = 1;
while (i - j >= 0 && heights[i - j] >= heights[i]) {
Left[i] += Left[i - j] + 1; // 左边连续的更高柱子的数量
j = Left[i] + 1; // 更左边的依旧可能更高的柱子
}
}
for (int i = len - 2; i >= 0; i--) {
int j = 1;
while (i + j < len && heights[i + j] >= heights[i]) {
Right[i] += Right[i + j] + 1; // 右边连续的更高柱子的数量
j = Right[i] + 1; // 更右边的依旧可能更高的柱子
}
}
int maxArea = INT_MIN;
for (int i = 0; i < len; i++) {
int Area = heights[i] * (1 + Left[i] + Right[i]); // 使用该柱子的高度
maxArea = max(maxArea, Area);
}
return maxArea;
}
};
矩阵中的最大矩形
- 问题:给定一个仅包含
0和1、大小为rows x cols的二维二进制矩阵,找出只包含1的最大矩形,并返回其面积。 - 解法:考虑矩阵中固定右下角的一族矩形,它们的极大面积等于该族矩形最右侧边的左侧连续
1数量的柱状图的最大矩形面积。因为矩阵是二进制的,因此只需扫描它的每一行,容易求出这种1的计数。然后把每一列视为柱状图,使用严格单调递增栈计算出某个柱子的左/右连续更高柱子的数量,从而计算出以这一列中的某点作为右下角的最大面积,最终找出全局最优解。
class Solution {
public:
int MaxArea(vector<int>& hist) {
int len = hist.size();
stack<pair<int, int>> stk; // 严格单调递增栈
stk.push({
-1, INT_MIN}); // 避免空栈(高度为负无穷)
vector<int> Left(len, 0);
for (int i = 0; i < len; i++) {
// 注意 i=0 也要压栈,虽然 Left[0]=0 平凡
int h = hist[i];
while (stk.top().second >= h) {
stk.pop(); // 移除它们,确保严格递增
}
Left[i] = (i - stk.top().first) - 1; // 和最近的更小值的距离,还需减 1
stk.push({
i, hist[i]});
}
stack<pair<int, int>>().swap(stk); // 因为 stack 没有 clear() 接口
stk.push({
len, INT_MIN}); // 避免空栈(高度为负无穷)
vector<int> Right(len, 0);
for (int i = len - 1; i >= 0; i--) {
// 注意 i=len-1 也要压栈,虽然 Right[len-1]=0 平凡
int h = hist[i];
while (stk.top().second >= h) {
stk.pop(); // 移除它们,确保严格递增
}
Right[i] = (stk.top().first - i) - 1; // 和最近的更小值的距离,还需减 1
stk.push({
i, hist[i]});
}
int res = INT_MIN;
for (int i = 0; i < len; i++) {
res = max(res, hist[i] * (1 + Left[i] + Right[i]));
}
return res;
}
int maximalRectangle(vector<vector<char>>& matrix) {
int rows = matrix.size();
int cols = matrix[0].size();
// 记录值是 '1' 的点,其向左边连续的 '1' 数量(含自己)
vector<vector<int>> LeftOne(cols, vector<int>(rows, 0));
for (int i = 0; i < rows; i++) {
int idx0 = -1;
for (int j = 0; j < cols; j++) {
char e = matrix[i][j];
if (e == '0') {
idx0 = j; // 左侧最近的 '0' 索引
} else {
LeftOne[j][i] = j - idx0; // 按列存储,第 j 列的柱状图
}
}
}
// 现在 LeftOne 的每一列都是一个柱状图,求其最大矩形面积
int res = INT_MIN;
for (auto& hist : LeftOne) {
res = max(res, MaxArea(hist));
}
return res;
}
};
哈希表
常数时间的增删和随机访问
-
问题:实现
RandomizedSet类:RandomizedSet()初始化RandomizedSet对象。bool insert(int val)当元素val不存在时,向集合中插入该项,并返回true;否则,返回false。bool remove(int val)当元素val存在时,从集合中移除该项,并返回true;否则,返回false。int getRandom()随机返回现有集合中的一项(测试用例保证调用此方法时集合中至少存在一个元素)。每个元素应该有相同的概率被返回。
你必须实现类的所有函数,并满足每个函数的平均时间复杂度为
O(1)。 -
解法:有序数组(
vector)提供了常数时间的随机访问,无序容器(unordered_set, unordered_map)提供了常数时间的插入/删除。结合两者:使用vector管理集合元素,使用map管理元素的存储位置。- 插入:简单的
push_back和insert即可; - 删除:首先根据
map把末尾元素和待删除元素交换(避免后续元素的大幅移动),然后再删除它,记得更新索引; - 访问:先查询
map获得索引,然后再检索vector获得元素。
- 插入:简单的
class RandomizedSet {
unordered_map<int, int> T; // 存储 val - idx,用于快速增删
vector<int> D; // 存储 val,用于随机访问
public:
RandomizedSet() {
T.clear();
D.clear();
}
bool insert(int val) {
if (T.find(val) == T.end()) {
T.insert({
val, D.size()});
D.push_back(val);
return true;
}
return false;
}
bool remove(int val) {
if (T.find(val) != T.end()) {
T[D.back()] = T[val];
swap(D[T[val]], D.back()); // 较待删除元素和末尾交换
D.pop_back(); // 然后删除末尾
T.erase(val);
return true;
}
return false;
}
int getRandom() {
int n = D.size();
int rd = rand() % n;
return D[rd];
}
};
最长连续序列
- 问题:给定一个未排序的整数数组
nums,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。请你设计并实现时间复杂度为O(n)的算法解决此问题。 - 解法:使用
sort可以获得平均意义下的拟线性复杂度。为了获得严格线性的复杂度,需要使用哈希表。关键在于:使用哈希表检索出连续序列的开头元素,从而确保每个元素仅被至多访问两次。
class Solution {
public:
int longestConsecutive(vector<int>& nums) {
unordered_set<int> S(nums.begin(), nums.end()); // 哈希表
int res = 0;
for (const int e : S) {
// 使用 (auto e: nums) 将会很慢!为何?
if (S.find(e - 1) != S.end()) // 本元素不是某连续序列的最小值
continue;
int cnt = 1;
while (S.find(e + cnt) != S.end()) {
// 每个元素至多执行这里一次,从而 O(n)
cnt++;
}
res = max(res, cnt);
}
return res;
}
};
随机链表的深复制
- 问题:给你一个长度为
n的链表,每个节点包含一个额外增加的随机指针random,该指针可以指向链表中的任何节点或空节点。构造这个链表的深拷贝。 深拷贝应该正好由n个全新节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的next指针和random指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。 - 解法:首先扫描一遍原始链表,构造出各自独立的新节点(不处理其指针),使用映射
map<Node*, Node*>记录原始节点到新节点的对应关系。然后,重新扫描一遍原始链表,处理其对应新节点中的指针。
/*
// Definition for a Node.
class Node {
public:
int val;
Node* next;
Node* random;
Node(int _val) {
val = _val;
next = NULL;
random = NULL;
}
};
*/
class Solution {
public:
Node* copyRandomList(Node* head) {
unordered_map<Node*, Node*> nodes; // 哈希表,记录新旧节点的对应关系
Node* ptr = head;
if (ptr == nullptr) // 不必复制,立即返回
return ptr;
Node* res = new Node(ptr->val); // 表头
nodes.insert({
ptr, res}); // ori - new
ptr = ptr->next;
while (ptr != nullptr) {
Node* nn = new Node(ptr->val);
nodes.insert({
ptr, nn});
ptr = ptr->next;
}
for (auto& p : nodes) {
if (p.first->next != nullptr) // 使用 ori->next 查询 new->next
p.second->next = nodes[p.first->next];
if (p.first->random != nullptr) // 同理
p.second->random = nodes[p.first->random];
}
return res;
}
};
深度优先搜索
岛屿数量
- 问题:给一个01矩阵,1代表是陆地,0代表海洋, 如果两个1相邻,那么这两个1属于同一个岛。我们只考虑上下左右为相邻。岛屿:相邻陆地可以组成一个岛屿(相邻:上下左右)。 判断岛屿个数。
- 解法:采用深度优先,遇到一个岛屿,则把它的陆地完全清理掉;继续找出下一个岛屿(与清理掉的岛屿不相连),直到找出全部岛屿。
#include <vector>
class Solution {
public:
void dfs(vector<vector<char>>& grid, int x, int y) {
int n = grid.size();
int m = grid[0].size();
#define check(X,Y) (((X)>=0 && (X)<n && (Y)>=0 && (Y)<m) && (grid[X][Y] == '1')) // 先判断索引范围,然后再检测表格的值(从前向后执行)
if (check(x, y - 1)) {
grid[x][y - 1] = '0';
dfs(grid, x, y - 1);
}
if (check(x, y + 1)) {
grid[x][y + 1] = '0';
dfs(grid, x, y + 1);
}
if (check(x - 1, y)) {
grid[x - 1][y] = '0';
dfs(grid, x - 1, y);
}
if (check(x + 1, y)) {
grid[x + 1][y] = '0';
dfs(grid, x + 1, y);
}
}
int solve(vector<vector<char>>& grid) {
int n = grid.size();
int m = grid[0].size();
int cnt = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (grid[i][j] == '1') {
grid[i][j] = '0';
cnt++;
dfs(grid, i, j); // 深度优先,把这个岛消除
}
}
}
return cnt;
}
};
素因子的最小和
- 问题:第一行一个正整数
n,代表

最低0.47元/天 解锁文章

被折叠的 条评论
为什么被折叠?



