一.数组、字符串
为什么把字符串和数组放在一起呢?因为很多时候要对字符串给个字符进行某种操作,遍历字符串并不方便,常把字符串转成字符数组。
1.1优点
- 构建一个数组非常简单
- 可以在O(1)的时间内根据数组的下标查询某个元素
1.2缺点
- 构建时必须分配一段连续的空间
- 查询某个元素是否存在时需要遍历整个数组,耗费O(n)的时间,n为元素的个数
- 删除和增添某个元素时同样需要耗费O(n)的时间
1.3应用场景
只需要进行访问数据操作时使用数组
1.4例题
力扣P242
思路:本题只是对字符串进行访问,不需要特殊操作。首先设置一个长度为26的judge数组记录每个字符串26个字母出现的次数;然后遍历两个字符串,第一个字符串中某字母出现一次就在judge对应下标加一,第二个字符串中某字母出现一次就在judge对应下标减一;最后判断judge数组中是否都为0。
class Solution {
public:
bool isAnagram(string s, string t) {
int judge[26]={0};
for(auto c:s)
judge[c-'a']++;
for(auto c:t)
judge[c-'a']--;
for(auto a:judge){
if(0!=a)
return false;
}
return true;
}
};
二.链表
链表就是为了避免数组的一大缺陷——开辟数组需要一片连续的空间,但也牺牲了数组的一些优点。
- 单链表:链表中为一个结点实际上是一个单独的对象,而所有对象都通过每个有元素中的引用字段链接在一起。
- 双链表:与单链表类似,但是每个结点中都含有两个引用字段。
2.1优点
- 灵活分配内存空间
- 能在O(1)时间内删除或者添加元素,前提:该元素前一个元素已知(单、双),该元素后一个元素已知(双)
2.2缺点
- 查询元素需要O(n)时间
2.3应用场景
数据元素个数不确定,需要经常添加和删除元素,不需要频繁查询数据时用链表
2.4例题
2.4.1解题技巧
- 利用快慢指针(链表翻转、访问链表倒数第k个元素或链表中间位置元素、判断是否有环)
- 构建一个虚假的头节点(需要返回一个新链表、合并两个有序链表、将链表的奇偶数按原定顺序分离为前后两部分)
2.4.2题目
力扣24
思路:分为递归和迭代两种思想
- 递归解法:将问题抽象为三个结点(代指1,2,3,其中3把其他结点抽象为一个结点)的链表,先将1接3,然后2接1,最后返回2。不断递归,节点为空或只剩一个结点作为递归出口。
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
if(head==NULL||head->next==NULL)
return head;
ListNode *pre=head;
ListNode *rear=head->next;
pre->next=swapPairs(rear->next);
rear->next=pre;
return rear;
}
};
- 迭代解法:思路与递归解法雷同
三.栈
3.1特点
先进后出,可以用单链表实现
3.2应用场景
当问题只关心最近一次的操作,而且需要在O(1)的时间内查找到更前的一次操作时使用栈
3.3例题
例一:力扣20
思路:这是一道经典的栈应用问题,因为一个表达式的括号匹配就是由内向外的。首先用字典map去存储左右括号的映射关系,然后遍历字符串遇见左括号压栈、右括号弹栈同时判断是否匹配,不匹配返回false,如果右括号全部被匹配,还要判断栈是否为空,空返回true,否则返回false。
class Solution {
public:
bool isValid(string s) {
map<char, char> map;
map[')'] = '(';
map[']'] = '[';
map['}'] = '{';
stack<char> st;
for(char c : s){
if(map.count(c)){
if(!st.empty() && map[c] == st.top())
st.pop();
else
return false;
}
else
st.push(c);
}
if(st.empty())
return true;
return false;
}
};
例二:力扣739
思路:遍历温度,如果比栈顶元素温度低就压栈;反之弹栈,直至栈顶元素比所遍历到的温度高或栈空停止弹栈,并压栈
class Solution {
public:
vector<int> dailyTemperatures(vector<int>& T) {
stack<int>si;
int n = T.size();
vector<int>res(n,0);
for (auto i = 0;i<n; ++i) {
while(!si.empty()&&T[i]>T[si.top()]){
res[si.top()]=i-si.top();
si.pop();
}
si.push(i);
}
return res;
}
};
四.队列
4.1普通队列
4.1.1特点
先进先出,可以用双链表实现
4.1.2应用场景
按一定顺序处理数据,而且数据在不断变化的时候(广度优先搜索)
4.2双端队列
4.2.1特点
头尾两端都可以在O(1)的时间内进行数据查看、添加和删除操作,也可以用双链表实现
4.2.2应用场景
实现一个长度动态变化的窗口或连续区间
4.2.3例题
力扣239
思路:题目所说的“窗口”,就是一直在变化的连续区间,如果遍历区间找到最大元素,时间复杂度是O(k*n),所以我们可以想到如果查询最大值可以在O(1)时间内完成,时间复杂度是不是就降下来了呢,我们想象一个线性结构,去存窗口元素,当遍历时该线性结构元素超过窗口大小,需要在前端删除元素;同时在还需要比较线性结构尾部的元素,尾部元素较大,就在尾部插入;尾部元素较小,就删除尾部元素,直至尾部元素较大或该线性结构为空。综上满足首尾可删除可插入的线性结构就是双端队列。
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
deque<int>di;
int n = nums.size();
vector<int>res;
for (int i = 0; i<n ; i++) {
if (!di.empty() && i - k + 1 > di.front())
di.pop_front();
while (!di.empty() && nums[i] > nums[di.back()])
di.pop_back();
di.push_back(i);
if (i >= k - 1)
res.push_back(nums[di.front()]);
}
return res;
}
};