总结:stl里数据结构,如hash(unordered_setmap),queue,deque,priority_queue,stack
主要会用以上数据结构的成员函数,empty(),count(),insert(),push(),push_back(),front(),top(),back(),push_front()
pop(),pop_back(),pop_front()等
c++ stl简介
在刷题时,我们几乎一定会用到各种数据结构来辅助我们解决问题,因此我们必须熟悉各种数据结构的特点。C++ STL提供的数据结构包括(实际
底层细节可能因编译器而异):
1.Sequence Containers:维持顺序的容器。
a.vector:动态数组,是我们最常使用的数据结构之一,用于O(1)的随机读取。因为大部分算法的时间复杂度都会大于O(n),因此我们经常
新建vector来存储各种数据或中间变量。因为在尾部增删的复杂度是O(1),我们也可以把它当作stack来用。
b.list:双向链表,也可以当作stack和queue来使用,由于LeetCode的题目多用Node表示链表,且链表不支持快速随机读取,因此用这个
数据结构。一个例外是经典的LRU问题,我们需要利用链表的特性来解决,我们在后文会遇到这个问题。
c.deque:双端队列,这是一个非常强大的数据结构,既支持O(1)随机读取,又支持O(1)时间的头部增删和尾部增删,不过有一定的额外开销,
d.array:固定大小的数组,一般在刷题时我们不使用。
e.forward_list:单向链表,一般在刷题时我们不使用。
2.Container Adaptors:基于其他容器实现的数据结构。
a.stack:后入先出(LIFO)的数据结构,默认基于deque实现。stack常用于深度优先搜索、一些字符串匹配问题以及单调栈问题。
b.queue:先入先出(FIFO)的数据结构,默认基于deque实现。queue常用于广度优先搜索。
c.priority_queue:最大值先出的数据结构,默认基于vector实现堆结构。它可以在O(nlogn)的时间排序数组,O(logn)的时间删除最大值。
priority_queue常用于维护数据结构并快速获取最大或最小值。
3.Associative Containers:实现了排好序的数据结构。
a.set:有序集合,元素不可重复,底层实现默认为红黑树,即一种特殊的二叉查找树(BST)。它可以在O(nlogn)的时间排序数组,O(logn)
的时间插入、删除、查找任意值,O(logn)的时间获得最小或最大值。这里注意,set和priority_queue都可以用于维护数据结构并快速获取最小或
最大值。但是它们的时间复杂度和功能略有不同。
b.multiset:支持重复元素的set。
c.map:有序映射或有序表,在set的基础上加上了映射关系,可以对每个元素key存一个值value。
d.multimap:支持重复元素的map。
4.Unordered Associative Containers:对每个Associative Containers实现了哈希版本。
a.unorded_set:哈希集合,可以在O(1)的时间快速插入、查找、删除元素,常用于快速查询一个元素是否在这个容器内。
b.unordered_multiset:支持重复元素的unordered_set。
c.unordered_map:哈希映射或哈希表,在unordered_set的基础上加上映射关系,可以对每一个元素key存一个值value。在某些情况下,
如果key的范围一致且较小,我们也可以用vector代替unordered_map,用位置表示key,用每个位置的值表示value。
d.unordered_multimap:支持重复元素的unordered_map。
448.找到所有数组中消失的数字
题目描述:给定一个长度为n的数组,其中包含范围为1到n的整数,有些整数重复了多次,有些整数没有出现,求1到n中没有出现过的整数。
输入输出样例:输入是一个一维整数数组,输出也是一个一维整数数组,表示输入数组内没有出现过的数字。
Input:[4,3,2,7,8,2,3,1]
Output:[5,6]
题解:把值转变为下标,通过正负号来标记是否出现过此数字,下标值为负代表出现过。
vector<int> findDisappearedNumbers(vector<int>& nums) {
vector<int> result; //返回值
for(int num:nums){ //遍历
int pos=abs(num)-1; //通过值转变为下标
if(nums[pos]>0){ //去重
nums[pos] = -nums[pos]; //通过正负号来标记是否出现过
}
}
for(int i=0; i<nums.size(); i++){
if(nums[i]>0){ //未出现
result.push_back(i+1);
}
}
return result;
}
48.旋转图像
题目描述:给定一个长度为n*n的矩阵,求它顺时针旋转90度的结果,且必须在原矩阵上修改(in-place) 。怎样能够尽量的不创建额外存储空间呢?
输入输出样例:输入输出都是一个二维整数矩阵。
Input:
[[1,2,3],
[4,5,6],
[7,8,9]]
Output:
[[7,4,1],
[8,5,2],
[9,6,3]]
题解:每次只考虑四个间隔90度的位置,可以进行O(1)额外空间的旋转。
void rotate(vector<vector<int>>& matrix){
int temp = 0,n=matrix.size()-1;
for(int i=0; i<= n/2; i++){
for(int j=i; j<n-i; j++){
temp=matrix[j][n-i];
matrix[j][n-i]=matrix[i][j];
matrix[i][j]=matrix[n-j][i];
matrix[n-j][i]=n[n-i][n-j];
matrix[n-i][n-j]= temp;
}
}
}
240.搜索二维矩阵
题目描述:给定一个二维矩阵,已知每行和每列都是增序的,尝试设计一个快速搜索一个数字是否在矩阵中存在的算法。
输入输出样例:输入是一个二维矩阵,和一个待搜索的整数。输出是一个布尔值,表示这个整数是否存在于矩阵中。
Input: matirx=
[[ 1, 4, 7,11,15],
[ 2, 5, 8,12,19],
[ 3, 6, 9,16,22],
[10,13,14,17,24],
[18,21,23,26,30]], target=5
Output:true
题解:这道题有一个简单的技巧:我们可以从右上角(或者左下角)开始查找,若当前值大于待搜索值,我们向左移动一位;若当前值小于待搜索值,我们向下移动一位。
如果最终移动到左下角时仍不等于待搜索值,则说明待搜索值不存在于矩阵中。
bool searchMatrix(vector<vector<int>>& matrix, int target){
int m=matirx.size();
if(m <= 0){
return false;
}
int n=matirx[0].size();
int i=0,j=n-1;
while(i<m && j>=0){
if(target == matrix[i][j]){ //选点(右上或左下)、二分查找方法
return true;
}else if(target > matirx[i][j]){
j--;
}else{
i++;
}
}
return false;
}
20.有效的括号
题目描述:给定一个由左右原括号、花括号和方括号组成的字符串,求这个字符串是否合法。合法的定义是每一个类型的左括号都有一个右括号
一一对应,且括号内的字符串也满足此要求。
输入输出样例:输入是一个字符串,输出是一个布尔值,表示字符串是否合法。
Input:"{[]}()"
Output:true
题解:括号是典型的使用栈来解决的问题。我们先从左往右遍历,每当遇到左括号便放入栈内,遇到右括号则判断其和栈顶的括号是否统一类型,是则从
栈内取出左括号,否则说明字符串不合法。
bool isValid(string s){
stack<char> parsed;
for(int i=0; i<s.length(); i++){
if(s[i]=='{' || s[i]=='[' || s[i]=='('){
parsed.push(s[i]);
}else{
if(parsed.empty()){
return false;
}
char c=parsed.top();
if((s[i]=='[' && c==']') || (s[i]=='{' && c=='}') || (s[i]=='(' && c==')')){
parsed.pop();
}else{
return false;
}
}
}
return parsed.empty();
}
739.每日温度
单调栈:通过维持栈内值的递增(递减)性,在整体O(n)的时间内处理需要大小比较的问题。
题目描述:给定每天的温度,求对于每一天需要等几天才可以等到更暖和的一天。如果该天之后不存在更暖和的天气,则记为0.
输入输出样例:输入是一个一维整数数组,输出是同样长度的整数数组,表示对于每天需要等待多少天。
Input:[73,74,74,71,69,72,76,73]
Output:[1,1,4,2,1,1,0,0]
题解:我们可以维持一个单调递减的栈,表示每天的温度;为了方便计算天数差,我们这里存放位置(即日期)而非温度本身。我们从左向右遍历
温度数组,对于每个日期p,如果p的温度比栈顶存储位置q的温度高,则我们取出q,并记录q需要等待的天数为p-q;我们重复这一过程,直到p的温度
小于等于栈顶存储位置的温度(或空栈)时,我们将p插入栈顶,然后考虑下一天。在这个过程中,栈内数组永远保持单调栈递减避免了使用排序进行
比较。最后若栈内剩余一些日期,则说明他们之后都没有出现过更暖和的日期。
vector<int> dailyTemperatures(vector<int>& temperatures){
int n=tempperparetures.size();
vector<int> ans(n);
stack<int> indices;
for(int i=0; i<n; i++){
while(!indices.empty()){
int pre_index = indices.top();
if(temperatures[i] <= temperatures[pre_index]){
break;
}
indices.pop();
ans[pre_index]=i-pre_index;
}
indices.push(i);
}
}
23.合并k个升序链表
题目描述:给定k个增序的链表,试将它们合成一条增序链表。
输入输出样例:输入是一个一维数组,每个位置存储链表的头节点;输出是一条链表。
Input:
[1->4->5,
1->3->4,
2->6]
Output:1->1->2->3->4->4->5->6
题解:本题可以有很多种解法,比如类似于归并排序进行两两合并。我们这里展示一个速度比较快的方法,即把所有的链表存储在一个优先队列中,
每次提取所有链表头部节点值最小的那个节点,直到所有链表都被提取完为止。注意因为Comp函数默认是对最大堆进行比较并维持递增关系,如果
我们想要获取最小的节点值,则我们需要实现一个最小堆,因此比较函数应该维持递减关系,所以operator()中返回时用大于号而不是小于号进行比较。
struct Comp{
bool operator() (ListNode* l1, ListNode* l2){
return l1->val > l2->val;
}
};
ListNode* mergeKLists(vector<ListNode*>& lists){
if(lists.empty()) return nullptr;
priority_queue<ListNode*, vector<ListNode*>, Comp> q;
for(ListNode* list:lists){
if(list){
q.push(list);
}
}
ListNode* dummy = new ListNode(0), *cur=dummy;
while(!q.empty()){
cur->next = q.top();
q.pop();
cur=cur->next;
if(cur->next){
q.push(cur->next);
}
}
return dummy->next;
}
239.双端队列
题目描述:给定一个整数数组和一个滑动窗口大小,求在这个窗口的滑动过程中,每个时刻其包含的最大值。
输入输出样例:输入是一个一维整数数组,和一个表示滑动窗口大小的整数;输出是一个一维整数数组,表示每个时刻的窗口内最大值。
Input:nums=[1,3,-1,-3,5,3,6,7], k=3
Output:[3,3,5,5,6,7]
题解:我们可以利用双端队列进行操作:每当向右移动时,把窗口左端的值从队列左端剔除,把队列右边小于窗口右端的值全部剔除。这样
双端队列的最左端永远是窗口内的最大值。另外,这道题也是单调栈的一种延伸:该双端队列利用从左到右递减来维持大小关系。
vector<int> maxSlidingWindow(vector<int>& nums, int k){
deque<int> dq;
vector<int> ans;
for(int i=0; i<nums.size(); i++){
if(!dp.empty() && dp.front() == i-k){ //窗口右移,剔除窗口左端的值
dp.pop_front();
}
while(!dp.empty() && nums[dp.back()]<nums[i]){ //把队列右边小于窗口右端的值全部剔除
dp.pop_back();
}
dp.push_back(i);
if(i >= k-1){
ans.push_back(nums[dp.front()]);
}
}
return ans;
}
1.两数之和:哈希表实现
题目描述:给定一个整数数组,已知有且只有两个数的和等于给定值,求这两个数的位置。
输入输出样例:输入一个一维整数数组和一个目标值,输出是一个大小为2的一维数组,表示满足条件的两个数字的位置。
Input: nums = [2,7,11,15], target=9
Output: [0,1]
在这个样例中,第0个位置的值2和第1个位置的值7的和为9。
题解:我们可以利用哈希表存储遍历过的值以及它们的位置,每次遍历到位置i的时候,查找哈希表里是否存在target-num[i],若存在
则说明这两个值的和为target。
vector<int> twoSum(vector<int>& nums, int target) {
//键是数字,值是该数字在数组的位置
unordered_map<int, int> hash; //定义一个hash的数组
vector<int> ans; //返回值
for(int i=0; i<nums.size(); i++){
int num = nums[i];
auto pos = hash.find(target - num);
if (pos == hash.end()){
hash[num] = i; //数组值作为key,下标作为value
}else{
ans.push_back(pos->second);
ans.push_back(i);
break;
}
}
return ans;
}
128.最长子序列
给定一个整数数组,求这个数组中的数字可以组成的最长连续序列有多长。
输入输出样例:输入是一个整数数组,输出是一个整数,表示连续序列的长度。
Input:[100,4,200,1,3,2] Output:4 在这个样例中,最长连续序列是[1,2,3,4].
题解:我们可以把所有的数字放在一个哈希表,然后不断的从哈希表中任意取一个值,并删掉其之前之后的所有连续数字,然后更新目前的最长连续
序列长度。重复这一过程,我们就可以找到所有的连续数字序列。
int longestConsecutive(vector<int>& nums) {
unordered_set<int> hash;
for(const int & num:nums){
hash.insert(num);
}
int ans=0;
while(!hash.empty()){
int cur=*(hash.begin());
hash.erase(cur);
int next=cur+1,prev=cur-1;
while(hash.count(next)){
hash.erase(next++);
}
while(hash.count(prev)){
hash.earse(prev--);
}
ans=max(ans, next-prev-1);
}
return ans;
}
560.和为k的子数组
题目描述:给定一个数组,寻找和为k的连续区间个数。
输入输出样例:输入一个一维整数数组和一个整数值k;输出一个整数,表示满足条件的连续区间个数。
Input: nums=[1,1,1],k=2
Output:2 在这个样例中,我们可以找到两个[1,1]连续区间满足条件。
题解:本题同样是利用前缀和,不同的是这里我们使用一个哈希表hashmap,其键是前缀和,而值是该前缀和出现的次数。在我们
遍历到位置i时,假设当前的前缀和是psum,那么hashmap[psum-k]即为以当前位置结尾、满足条件的区间个数。
int subarraySum(vector<int>& nums, int k){
int count=0, psum=0;
unordered_map<int, int> hashmap;
hashmap[0]=1; //初始化很重要
for(int i:nums){
psum += i;
count += hashmap[psum-k];
++hashmap[psum];
}
return count;
}
类型:
- 贪心算法
- 双指针
- 二分查找
- 排序算法
- 二维数组的搜索
- 动态规划
- 数据结构-stl
- 字符串比较、匹配
- 链表
- 二叉树