写这篇的时候,开始正式准备复习数据结构了,其实复习数据结构的最好方式就是写算法题,编程能力和数据结构都能够提升,当然,这部分的题目也相对来说比较简单。
基础的数据结构主要包括数组,哈希表,链表,字符串,栈与队列等,当然也会包括一些高级数据结构比如堆、图之类的。
这里将二者放到一起来写,是觉得哈希有时候作为一种辅助数据结构帮忙解决数组类题目。
数组常用思想有:重建数组, 双指针, 滑动窗口, 常用的工具,字典,集合
Leetcode 283. 移动零
Leetcode 26. 删除有序数组中的重复项
剑指 Offer 21. 调整数组顺序使奇数位于偶数前面
Leetcode 11. 盛最多水的容器
Leetcode 15. 三数之和
Leetcode 16. 最接近的三数之和
Leetcode 88. 合并两个有序数组
Leetcode 4. 寻找两个正序数组的中位数
Leetcode 189. 轮转数组
Leetcode 48. 旋转图像
Leetcode 54. 螺旋矩阵
Leetcode 59. 螺旋矩阵 II
Leetcode 498. 对角线遍历
Leetcode 66. 加一
Leetcode 43. 字符串相乘
Leetcode 7. 整数反转
Leetcode 166. 分数到小数
剑指 Offer 43. 1~n 整数中 1 出现的次数
Leetcode 400. 第 N 位数字
Leetcode 169. 多数元素
Leetcode 470. 用 Rand7() 实现 Rand10()
滑动窗口模板:
/* 滑动窗口算法框架 */
void slidingWindow(string s) {
// 用合适的数据结构记录窗口中的数据
unordered_map<char, int> window;
int left = 0, right = 0;
while (right < s.size()) {
// c 是将移入窗口的字符
char c = s[right];
window.add(c)
// 增大窗口
right++;
// 进行窗口内数据的一系列更新
...
/*** debug 输出的位置 ***/
// 注意在最终的解法代码中不要 print
// 因为 IO 操作很耗时,可能导致超时
printf("window: [%d, %d)\n", left, right);
/********************/
// 判断左侧窗口是否要收缩
while (left < right && window needs shrink) {
// d 是将移出窗口的字符
char d = s[left];
window.remove(d)
// 缩小窗口
left++;
// 进行窗口内数据的一系列更新
...
}
}
}
Leetcode 209. 长度最小的子数组
Leetcode 76. 最小覆盖子串(⭐⭐⭐⭐⭐)
Leetcode 567. 字符串的排列
Leetcode 438. 找到字符串中所有字母异位词
Leetcode 3. 无重复字符的最长子串
Leetcode 713. 乘积小于 K 的子数组
哈希表在C++STL里面就是unordered_map
和unordered_set
,下面罗列一下常用方法。
由于unordered_map
和unordered_set
用法基本一致,下面讲解一下unordered_map
的方法。
// 建立
unordered_map<int,int> m;
unordered_map<int, string> map2 = {
{2, "cherry"},
{3, "peach"},
{1, "melon"}
};
// 添加
m.insert(pair<int, int>(1, 10)); // .insert函数
m[3] = 30; // 数组方式直接添加(unordeed_set没有这个)
// 成员函数
m.begin() // 返回指向容器中第一个键值对的正向迭代器
m.end() // 返回指向容器中最后一个键值对之后位置的正向迭代器
m.find(2) // 查找key为2的键值对是否存在,若没有则返回m.end()
if(m.find() != m.end()) // 判断找到了key = 2的键值对
m.count(3) // 查找key为3的键值对,找到返回1,反之0
m.clear() // 清空哈希表
// swap函数
unordered_map<int,int> m2;
m1.swap(m2);
swap(m1,m2);
// 哈希表的遍历
for(auto p : m) { // 第一种
int key = p.first, value = p.second;
}
for(auto it = m.begin(); it != m.end(); it++) { // 第二种
int key = p.first, value = p.second;
}
Leetcode 128. 最长连续序列
Leetcode 49. 字母异位词分组
Leetcode 560. 和为 K 的子数组
Leetcode 242. 有效的字母异位词
Leetcode 349. 两个数组的交集
Leetcode 202. 快乐数
Leetcode 454. 四数相加 II
Leetcode 383. 赎金信
Leetcode 剑指 Offer 03. 数组中重复的数字
Leetcode 442. 数组中重复的数据
Leetcode 41. 缺失的第一个正数
Leetcode 448. 找到所有数组中消失的数字
Leetcode 287. 寻找重复数
Leetcode 162. 寻找峰值
Leetcode 238. 除自身以外数组的乘积
Leetcode 剑指 Offer 51. 数组中的逆序对
栈在C++STL中的应用是stack
,下面讲解一下常用方法。
#include
stack<int> q; //以int型为例
int x;
q.push(x); //将x压入栈顶
q.top(); //返回栈顶的元素
q.pop(); //删除栈顶的元素
q.size(); //返回栈中元素的个数
q.empty(); //检查栈是否为空,若为空返回true,否则返回false
Leetcode 20. 有效的括号
Leetcode1249. 移除无效的括号
Leetcode 1047. 删除字符串中的所有相邻重复项
Leetcode 739. 每日温度
Leetcode 84. 柱状图中最大的矩形(⭐⭐⭐⭐⭐)
Leetcode 85. 最大矩形
Leetcode 42. 接雨水(⭐⭐⭐⭐⭐)
Leetcode 150. 逆波兰表达式求值
Leetcode 227. 基本计算器 II
Leetcode 面试题 03.05. 栈排序
Leetcode 394. 字符串解码
Leetcode 946. 验证栈序列
Leetcode 155. 最小栈(⭐⭐⭐⭐⭐)
Leetcode 232. 用栈实现队列
Leetcode 402. 移掉 K 位数字
牛客 BM49 表达式求值
Leetcode 316. 去除重复字母
队列在C++STL中的应用是queue
,还有个双向队列deque
,这里我都讲一下。
#include
queue<int> q;
int x;
q.push(x); // 在队尾插入一个元素
q.pop(); // 删除队列中第一个元素
q.size(); // 返回队列中元素个数
q.empty(); // 若为空返回true
q.front(); // 返回队列中第一个元素
q.back(); // 返回队尾元素
#include
deque<int> q;
int x;
q.begin(); q.end(); // deque的首元素和尾元素下一个位置的迭代器
q.rbegin(); q.rend(); // deuqe尾元素和头元素前一个结点的迭代器
q.size(); // 容器多少元素
q.empty(); // 若为空返回true
q.front(); // 返回队列中第一个元素
q.back(); // 返回队尾元素
q.push_back(x); q.push_front(x); // 在队尾、队首插入元素
q.emplace(iterator, x); // C++11 右值 效率更高
- emplace_back(x)
- emplace_front(x)
q.pop_back(); q.pop_front(); // 在队尾、堆首弹出元素
q.insert();
- insert(iterator,value);
- insert(iterator, num, value);
- insert(iterator, iterator1, iterator2);
还有个非常非常非常重要的数据结构——优先队列,做题时也可以自己用单调栈/单调队列实现,在queue
中,必须掌握!
#include
priority_queue<int> q;
q.top(); // 返回首元素
q.push(); // 插入元素
q.emplace(); // 插入元素(尽量用emplace代替push,更快)
q.size(); // 容器元素个数
q.empty(); // 是否为空
/*
用pair做优先队列的元素
规则:pair的比较,先比较第一个元素,第一个相等比较第二个。
*/
priority_queue<pair<int, int>> a;
a.emplace(1, 2);
a.emplace(make_pair(3, 4));
a.emplace(pair<int, int>(5, 6));
while (!a.empty())
{
cout << a.top().first << ' ' << a.top().second << '\n';
a.pop();
}
/*
自定义排序
优先队列默认是大根堆,即less,若想使用小根堆,需要用greater
注意,vector中的T要和第一个参数一致
*/
#include
#include
priority_queue<int,vector<int>,greater<int>> small_heap; // 小根堆,升序
priority_queue<int,vector<int>,less<int>> big_heap; // 大根堆,降序
/*
匿名函数,用得比较少,可以看下这个题的用法:https://blog.csdn.net/weixin_51322383/article/details/130974094
*/
auto cmp = [&](const int& a, const int &b) {
return cnt[a] < cnt[b];// 此处cnt可由上文完成定义(最大堆--跟sort正好相反)
};
priority_queue<int, vector<int>, decltype(cmp)> pq(cmp); // 这句代码很重要,需要加参数
Leetcode 239. 滑动窗口最大值(⭐⭐⭐⭐⭐)
剑指 Offer 59 - II. 队列的最大值(⭐⭐⭐⭐⭐)
Leetcode 862. 和至少为 K 的最短子数组
Leetcode 225. 用队列实现栈
字符串类型的题,主要有字符串的旋转与替换, 字符串的匹配,字符串的覆盖, 涉及到的解法双指针反转,KMP匹配,滑动窗口找覆盖等。
Leetcode 541. 反转字符串 II
Leetcode 151. 反转字符串中的单词
剑指 Offer 58 - II. 左旋转字符串
Leetcode 8. 字符串转换整数 (atoi)
Leetcode 468. 验证IP地址
Leetcode 415. 字符串相加
Leetcode 10. 正则表达式匹配
剑指 Offer 20. 表示数值的字符串
Leetcode 28. 找出字符串中第一个匹配项的下标 —— KMP(⭐⭐⭐⭐⭐)
之前在数组篇就刷过滑动窗口了,对于滑动窗口,还是那四个问题:
①当移动right扩大窗口时, 应该怎么更新?
② 什么条件下,窗口应该暂停扩大,开始移动left缩小窗口?
③当移动left缩小窗口时, 应该怎么更新?
④我们要的结果应该在扩大窗口时更新还是在缩小窗口时更新?
Leetcode 76. 最小覆盖子串(⭐⭐⭐⭐⭐)
Leetcode 567. 字符串的排列
Leetcode 209. 长度最小的子数组
Leetcode 438. 找到字符串中所有字母异位词
Leetcode 3. 无重复字符的最长子串
Leetcode 713. 乘积小于 K 的子数组
前缀的思想和滑动窗口类似,都是用在子数组和子串问题上面,当遇到满足某条件的连续子数组、子串灯等,就应该联想到这两种方法。但是两种方法又有一些差别(比如说有负数,就不能用滑动窗口),需要好好甄别。
假设我们前缀和数组保存前n nn项的和, presum[1]
保存nums数组前1位的和, presum[2]=nums[0]+nums[1]=presum[1]+nums[1]
, 这样,通过前缀和数组,就能轻松得到每个区间的和。
比如, 求nums[2]
到nums[4]
区间的和,可以直接presum[5]-presum[2]
得到。
Leetcode 560. 和为 K 的子数组
Leetcode 724. 寻找数组的中心下标
Leetcode 1248. 统计「优美子数组」
Leetcode 974. 和可被 K 整除的子数组
Leetcode 523. 连续的子数组和
Leetcode 437. 路径总和 III
这部分主要是一些业务题,没什么算法难度,主要在于细节:大小写转换、char与string的转换等等。
Leetcode 831. 隐藏个人信息
Leetcode 240. 搜索二维矩阵 II
Leetcode 73. 矩阵置零
Leetcode 207. 课程表
Leetcode 208. 实现 Trie (前缀树)
Leetcode 347. 前 K 个高频元素
Leetcode 295. 数据流的中位数
Leetcode 215. 数组中的第K个最大元素
Leetcode 912. 排序数组 —— 总结十大排序
Leetcode 75. 颜色分类
牛客 BM52 数组中只出现一次的两个数字
References
算法刷题重温(十): 回归基础数据结构之数组和哈希表