关于栈的详细理论分析,详见此篇博客
不使用任何内建的哈希表库设计一个哈希集合(HashSet)。
实现 MyHashSet 类:
- void add(key) 向哈希集合中插入值 key 。
- bool contains(key) 返回哈希集合中是否存在这个值 key 。
- void remove(key) 将给定值 key 从哈希集合中删除。如果哈希集合中没有这个值,什么也不做。
为了实现哈希集合这一数据结构,有以下几个关键问题需要解决:
哈希函数:能够将集合中任意可能的元素映射到一个固定范围的整数值,并将该元素存储到整数值对应的地址上。
冲突处理:由于不同元素可能映射到相同的整数值,因此需要在整数值出现「冲突」时,需要进行冲突处理。总的来说,有以下几种策略解决冲突:
2.1 链地址法:为每个哈希值维护一个链表,并将具有相同哈希值的元素都放入这一链表当中。 2.2 开放地址法:当发现哈希值 hh 处产生冲突时,根据某种策略,从 hh 出发找到下一个不冲突的位置。 例如,一种最简单的策略是,不断地检查 h+1,h+2,h+3,\ldotsh+1,h+2,h+3,… 这些整数对应的位置。 2.3 再哈希法:当发现哈希冲突后,使用另一个哈希函数产生一个新的地址。 2.4 扩容:当哈希表元素过多时,冲突的概率将越来越大,而在哈希表中查询一个元素的效率也会越来越低。 因此,需要开辟一块更大的空间,来缓解哈希表中发生的冲突。
以上内容读者可以自行翻阅数据结构的教材,本题解不再阐述,而是直接给出一个最简单的哈希表实现。
方法一:链地址法
设哈希表的大小为 base,则可以设计一个简单的哈希函数:hash(x) = x mod base。
我们开辟一个大小为 base 的数组,数组的每个位置是一个链表。当计算出哈希值之后,就插入到对应位置的链表当中。
由于我们使用整数除法作为哈希函数,为了尽可能避免冲突,应当将 base 取为一个质数。在这里,我们取 base=769。
class MyHashSet {
private:
vector<list<int>> data;//每个位置是链表的数组
static const int base = 769;//取模运算的基本值,也是数组的大小,最好取质数,减少重复
static int hash(int key){//哈希函数:将输入的key映射的一组整数范围内的一个值
return key % base;
}
public:
//不定长拉链法
/** Initialize your data structure here. */
MyHashSet() {
data.resize(base);
}
void add(int key) {
int h = hash(key);
for(auto it = data[h].begin(); it != data[h].end(); it++){
if((*it) == key) return;
}
data[h].push_back(key);
}
void remove(int key) {
int h = hash(key);
for(auto it = data[h].begin(); it != data[h].end(); it++){
if((*it) == key){
data[h].erase(it);
return;
}
}
}
/** Returns true if this set contains the specified element */
bool contains(int key) {
int h = hash(key);
for(auto it = data[h].begin(); it != data[h].end(); it++){
if((*it) == key) return true;
}
return false;
}
};
其他方法,可参考其他的题解
不使用任何内建的哈希表库设计一个哈希映射(HashMap)。
实现 MyHashMap 类:
MyHashMap() 用空映射初始化对象 void put(int key, int value) 向 HashMap 插入一个键值对
(key, value) 。如果 key 已经存在于映射中,则更新其对应的值 value 。 int get(int key) 返回特定的
key 所映射的 value ;如果映射中不包含 key 的映射,返回 -1 。 void remove(key) 如果映射中存在 key
的映射,则移除 key 和它所对应的 value 。
如果做过上面那道705题,那么「设计哈希映射」与「设计哈希集合」解法接近,唯一的区别在于我们存储的不是 key 本身,而是 (key,value) 对。除此之外,代码基本是类似的。
class MyHashMap {
private:
vector<list<pair<int, int>>> data;//拉链法:里面pair存键值对
static const int base = 769;//取模运算作哈希映射
static int hash(int key){
return key % base;
}
public:
/** Initialize your data structure here. */
MyHashMap() {
data.resize(base);
}
/** value will always be non-negative. */
void put(int key, int value) {
int h = hash(key);
for(auto it = data[h].begin(); it != data[h].end(); it++){
if((*it).first == key){
(*it).second = value;
return;
}
}
data[h].push_back(make_pair(key, value));
}
/** Returns the value to which the specified key is mapped, or -1 if this map contains no mapping for the key */
int get(int key) {
int h = hash(key);
for(auto it = data[h].begin(); it != data[h].end(); it++){
if((*it).first == key){
return (*it).second;
}
}
return -1;
}
/** Removes the mapping of the specified value key if this map contains a mapping for the key */
void remove(int key) {
int h = hash(key);
for(auto it = data[h].begin(); it != data[h].end(); it++){
if((*it).first == key){
data[h].erase(it);
return;
}
}
}
};
请你仅使用两个栈实现先入先出队列。队列应当支持一般队列的支持的所有操作(push、pop、peek、empty):
实现 MyQueue 类:
void push(int x) 将元素 x 推到队列的末尾; int pop() 从队列的开头移除并返回元素; int peek()返回队列开头的元素; boolean empty() 如果队列为空,返回 true ;否则,返回 false
说明:
你只能使用标准的栈操作 —— 也就是只有 push to top, peek/pop from top, size, 和 is empty操作是合法的。 你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。
进阶:
你能否实现每个操作均摊时间复杂度为 O(1) 的队列?换句话说,执行 n 个操作的总时间复杂度为 O(n),即使其中一个操作可能花费较长时间。
两个数据结构的概念:
- 栈:后进先出
- 队列:先进先出
题目让我们用栈来实现一个队列,就是要让两个栈实现一个先进先出的数据结构。
思路是:
「输入栈」会把输入顺序颠倒;如果把「输入栈」的元素逐个弹出放到「输出栈」,再从「输出栈」弹出元素的时候,则可以负负得正,实现了先进先出。
无论「用栈实现队列」还是「用队列实现栈」,思路都是类似的。
都可以通过使用两个栈/队列来解决。
我们创建两个栈,分别为 out 和 in,用作处理「输出」和「输入」操作。
其实就是两个栈来回「倒腾」。
而对于「何时倒腾」决定了是 O(n) 解法 还是 均摊 O(1) 解法。
1. O(n) 解法
我们创建两个栈,分别为 out 和 in:
- in 用作处理输入操作 push(),使用 in 时需确保 out 为空
- out 用作处理输出操作 pop() 和 peek(),使用 out 时需确保 in 为空
2. 均摊 O(1) 解法
事实上,我们不需要在每次的「入栈」和「出栈」操作中都进行「倒腾」。
我们只需要保证,输入的元素总是跟在前面的输入元素的后面,而输出元素总是最早输入的那个元素即可。
可以通过调整「倒腾」的时机来确保满足上述要求,但又不需要发生在每一次操作中:
只有在「输出栈」为空的时候,才发生一次性的「倒腾」
//思路:inStack负责存push进的新元素;pop,peek:将in中的依次存到outStack中,顺序刚好调换过来,符合队列的要求,此时栈顶即为队首元素;当out空时再从in里移入
class MyQueue {
private:
stack<int> inStack, outStack;//双栈实现队列功能
//实现inStack移入到outStack的功能
void in2out(stack<int>& inStack, stack<int>& outStack){
while(!inStack.empty()){
outStack.push(inStack.top());
inStack.pop();
}
}
public:
/** Initialize your data structure here. */
MyQueue() {}
/** Push element x to the back of queue. */
void push(int x) {
inStack.push(x);
}
/** Removes the element from in front of queue and returns that element. */
int pop() {
if(outStack.empty()){
in2out(inStack, outStack);
}
int x = outStack.top();
outStack.pop();
return x;
}
/** Get the front element. */
int peek() {
if(outStack.empty()){
in2out(inStack, outStack);
}
return outStack.top();
}
/** Returns whether the queue is empty. */
bool empty() {
return inStack.empty() && outStack.empty();
}
};
暂未记录
根据 逆波兰表示法,求表达式的值。
有效的运算符包括 +, -, *, / 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。
- 说明:
整数除法只保留整数部分。
给定逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。
逆波兰表达式:
逆波兰表达式是一种后缀表达式,所谓后缀就是指算符写在后面。
平常使用的算式则是一种中缀表达式,如 ( 1 + 2 ) * ( 3 + 4 ) 。
该算式的逆波兰表达式写法为 ( ( 1 2 + ) (3 4 + ) * ) 。
逆波兰表达式主要有以下两个优点:去掉括号后表达式无歧义,上式即便写成 1 2 + 3 4 + * 也可以依据次序计算出正确结果。 适合用栈操作运算:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中。
我们平常用的是中缀表达式,也就是从小到大数学课本里
计算式子。题目中的是逆波兰式,也叫后缀表达式,一个好处就是只需要运算符,去掉括号,也不会产生歧义。
计算法则就是,每次找到运算符位置的前两个数字,然后进行计算。 该后缀表达式是非常符合计算机计算逻辑的。
- 直接用栈写了代码,遇到操作数就入栈,遇到操作符就将栈顶的两个元素弹出进行操作,将结果继续入栈即可。
后面其他计算器相关题,其中一个解法就是 将其字符串 转化成 后缀表达式,再计算。
本题中每一个子表达式要得出一个结果,然后拿这个结果再进行运算,那么这岂不就是一个相邻字符串消除的过程,这和 1047. 删除字符串中的所有相邻重复项 是差不多的,只不过本题不要相邻元素做消除了,而是做运算!
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<int> num;//数字栈
int ans;
for(const string& s : tokens){
if(s == "+" || s == "-" || s == "*" || s == "/"){
int b = num.top();num.pop();
int a = num.top();num.pop();
//取栈顶两个值计算后再入栈
if(s == "+")
num.push(a+b);
else if(s == "-")
num.push(a-b);
else if(s == "*")
num.push(a*b);
else if(s == "/")
num.push(a/b);
}else{
num.push(stoi(s));
}
}
ans = num.top();
return ans;
}
};
实现一个基本的计算器来计算一个简单的字符串表达式的值。
字符串表达式仅包含非负整数,+, - ,*,/ 四种运算符和空格 。 整数除法仅保留整数部分。
计算器的题目基本都和栈有关,这道题也不例外。
由题目信息可知,s 中一共包含以下几种数据: 空格 数字 操作符。
这里有 + - * / 而对于操作符来说又可以进一步细分:
- 一元操作符 + -
- 二元操作符 * /
1.对于一元操作符来说,我们只需要知道一个操作数即可。这个操作数就是操作符右边的数字。为了达到这个效果,我们需要一点小小的 trick。
1 + 2 我们可以在前面补充一个 + 号,变成:+1 + 2 可看成 (+1)(+2) 再比如: (-1)(+2)(+3)(-4) 括号只是逻辑分组,实际并不存在。下同,不再赘述。
2.而对于二元操作符来说,我们需要知道两个操作数,这两个操作数分别是操作符两侧的两个数字。
(5) / (2) 再比如 (3) * (4)
简单来说就是,一元操作符绑定一个操作数。而二元操作符绑定两个操作数。
算法:
- 从左到右遍历 s :
- 如果是数字,则更新数字
- 如果是空格,则跳过
- 如果是运算符,则按照运算符规则计算,并将计算结果重新入栈,具体见代码。最后更新 pre_flag 即可。(此处比较的永远是 当前数前面一个操作符,所以最后要加一个哨兵,不然最后一个数会漏掉没压入栈中)
为了简化判断,使用了两个哨兵。一个是 s 末尾的$,另一个是最开始的 pre_flag。trick解析:
- 记录 pre_flag,即上一次出现的操作符
- 使用哨兵简化操作。一个是 s 的 $ ,另一个是 pre_flag 的 +
class Solution {
public:
int calculate(string s) {
stack<int> numStk;//存数字的栈
//num和pre_op的预先初始化(加个哨兵),可以解决s开头是负数的情况
s += '$';//给原字符串最后加个哨兵,是为了把最后一个数压进栈里
int num = 0;//计算当前数的值,初始化为0
char pre_op = '+';//记录的总是当前数 前面的操作符,初始化为+号
for(const auto& c : s){
if(c == ' ') continue;//空格跳过
else if(c >= '0' && c<= '9')//数字就统计
num = num * 10 + (c - '0');
else{//遇到计算符号时,判断的是当前数Num之前的计算符,即可拿栈顶元素与当前数num直接算乘除
if(pre_op == '+')
numStk.push(num);
else if(pre_op == '-')
numStk.push(-num);
else if(pre_op == '*'){
int pre = numStk.top();//取出前一个数
numStk.pop();
numStk.push(pre*num);
}else if(pre_op == '/'){
int pre = numStk.top();
numStk.pop();
numStk.push(pre/num);
}
num = 0;//入栈一个数后,勿忘置0重新统计后面的数
pre_op = c;//更新下一个操作符状态
}
}
int ans = 0;
//将栈中数全部累加即可得到答案
while(!numStk.empty()){
ans += numStk.top();
numStk.pop();
}
return ans;
}
};
实现一个基本的计算器来计算一个简单的字符串表达式 s 的值。
这道题是上面 227. 基本计算器 II 的扩展版,多了括号而已。不妨先做一下 227. 基本计算器 II 入手。 拿题目中的例子来说
“(1+(4+5+2)-3)+(6+8)”。我们可以将其拆分为:(6+8 )= 14
(4 + 5 + 2) = 11
(11)- 3 = 8
(1 + 8) = 9
(9 + 14) = 23
简单来说就是将括号里面的内容提取出来,提取出来就是上面的问题了。用上面的方法计算出结果,然后将结果作为一个数字替换原来的表达式。
比如我们先按照上面的算法计算出 6 + 8 的结果是 14,然后将 14 替换原来的(6+8),那么原问题就转化为了(1+(4+5+2)-3)+14 。这样一步一步就可以得到答案。因此我们可以使用递归,每次遇到 ‘(’ 则开启一轮新的递归,遇到 ‘)’ 则退出一层递归即可。
通过递归实现 由内层(…)到外层(…)值的计算:
- 遇到‘(’ 就递归调用本身计算的函数,勿忘下标的后移;
- 遍历代码块最后 遇到‘)’就跳出循环(代表当前内层括号内的值以全压入栈中),计算累加值并返回;
详见代码 和 详细注释~
class Solution {
public:
int dfs(string& s, int& i){
stack<int> numStk;//存数字的栈
//num和pre_op的预先初始化(加个哨兵),可以解决s开头是负数的情况
int num = 0;//计算当前数的值,初始化为0
char pre_op = '+';//记录的总是当前数 前面的操作符,初始化为+号
for(; i < s.size(); i++){
if(s[i] == ' ') continue;
if(s[i] == '('){//遇到(,下一个数字开始递归
num = dfs(s, ++i);
i++;//递归结束时在')' 要i++
}
if(isdigit(s[i]))//遇到数字,isdigit()可以判断char类型
num = num * 10 + (s[i] - '0');
else{//遇到其他符号
if(pre_op == '+')//
numStk.push(num);
else if(pre_op == '-')
numStk.push(-num);
else if(pre_op == '*'){//* /题中没有,可以不写
int pre = numStk.top();//取出前一个数
numStk.pop();
numStk.push(pre*num);
}else if(pre_op == '/'){
int pre = numStk.top();
numStk.pop();
numStk.push(pre/num);
}
num = 0;//入栈一个数后,勿忘置0重新统计后面的数
pre_op = s[i];//更新下一个操作符状态
}
if(s[i] == ')') break;//递归终止条件:找到一对内层(...)括号对,就退出循环,算出该括号内的值
}
int ans = 0;
//计算当前栈中的值
while(!numStk.empty()){
ans += numStk.top();
numStk.pop();
}
return ans;
}
int calculate(string s) {
s += "$";//末尾加个哨兵,是为了把最后一个数压进栈里
int i = 0;
return dfs(s, i);//不能直接传0,因为非常量引用的初始值必须为左值
}
};
- 利用栈完成中缀表达式到后缀表达式的转换;
- 利用栈对后缀表达式完成计算。
#include
#include
#include
using namespace std;
class Solution {
private:
stack<int> num;
stack<char> op;
int pri(char a){
switch(a){
case '+': return 1;
case '-': return 1;
case '*': return 2;
case '/': return 2;
case '(': return 3;
default: return -1;
}
}
void cal(){
int b=num.top();num.pop();
int a=num.top();num.pop();
switch(op.top()){
case '+':num.push(a+b);break;
case '-':num.push(a-b);break;
case '*':num.push(a*b);break;
case '/':num.push(a/b);break;
}
op.pop();
}
public:
int calculate(string s) {
string ss;
for(int i=0;i<(int)s.size();i++){
if(isdigit(s[i]))
ss+=s[i];
else if(s[i]==' ') continue;
else{
if(!ss.empty()){
num.push(stoi(ss));
ss.clear();
}
if(op.empty()||s[i]=='('|| pri(op.top())<pri(s[i]) )
op.push(s[i]);
else if(s[i]==')'){
while(op.top()!='(') cal();
op.pop();
}
else{
while(!op.empty()&&pri(op.top())<=pri(s[i])) cal();
op.push(s[i]);
}
}
}
if(!ss.empty()) num.push(stoi(ss));
while(!op.empty()) cal();
return num.top();
}
};
int main(){
Solution s;
cout<<s.calculate("(1+(4+5+2)-3)+(6+8)")<<endl;
}
其他更多解法详见 此解答。
序列化二叉树的一种方法是使用前序遍历。当我们遇到一个非空节点时,我们可以记录下这个节点的值。如果它是一个空节点,我们可以使用一个标记值记录,例如#。
例如,上面的二叉树可以被序列化为字符串 “9,3,4,#,#,1,#,#,2,#,6,#,#”,其中 # 代表一个空节点。
给定一串以逗号分隔的序列,验证它是否是正确的二叉树的前序序列化。编写一个在不重构树的条件下的可行算法。
每个以逗号分隔的字符或为一个整数或为一个表示 null 指针的 ‘#’ 。
你可以认为输入格式总是有效的,例如它永远不会包含两个连续的逗号,比如 “1,3” 。
我们可以定义一个概念,叫做槽位。一个槽位可以被看作「当前二叉树中正在等待被节点填充」的那些位置。
二叉树的建立也伴随着槽位数量的变化。每当遇到一个节点时:我们使用栈来维护槽位的变化。栈中的每个元素,代表了对应节点处剩余槽位的数量,而栈顶元素就对应着下一步可用的槽位数量:
- 当遇到空节点时,仅将栈顶元素减 1;
- 当遇到非空节点时,将栈顶元素减 1 后,再向栈中压入一个 2。
- 无论何时,如果栈顶元素变为 0,就立刻将栈顶弹出。
遍历结束后:
- 若栈为空,说明没有待填充的槽位,因此是一个合法序列;
- 否则若栈不为空,则序列不合法。
- 此外,在遍历的过程中,若槽位数量不足,则序列不合法。
class Solution {
public:
bool isValidSerialization(string preorder) {
int n = preorder.length();
int i = 0;
stack<int> stk;
stk.push(1);
while (i < n) {
if (stk.empty()) {
return false;
}
if (preorder[i] == ',') {
i++;
} else if (preorder[i] == '#'){
stk.top() -= 1;
if (stk.top() == 0) {
stk.pop();
}
i++;
} else {
// 读一个数字
while (i < n && preorder[i] != ',') {
i++;
}
stk.top() -= 1;
if (stk.top() == 0) {
stk.pop();
}
stk.push(2);
}
}
return stk.empty();
}
};
能否将方法一 时间空间复杂度都是O(n) 的空间复杂度优化至 O(1) 呢?
回顾方法一的逻辑,如果把栈中元素看成一个整体,即所有剩余槽位的数量,也能维护槽位的变化。
因此,我们可以只维护一个计数器,代表栈中所有元素之和,其余的操作逻辑均可以保持不变。
class Solution {
public:
//加入一个数就多两个槽位,并占去一个槽位;遇到#(叶子节点)就减少一个槽位
bool isValidSerialization(string preorder) {
int n = preorder.size(), i = 0;
int slots = 1;//初始槽位为1,放根节点
while(i < n){
if(slots == 0)//若还没遍历完就没有槽位了,
return false;
if(preorder[i] == ',')//','跳过
i++;
else if(preorder[i] == '#'){//‘#’槽位减1
slots--;
i++;
}else{//数字可能是多位,需要判断连续读取
while(i < n && preorder[i] != ','){
i++;
}
slots++;//slots = slots - 1 + 2;
}
}
return slots == 0;
}
};
请你给一个停车场设计一个停车系统。停车场总共有三种不同大小的车位:大,中和小,每种尺寸分别有固定数目的车位。
请你实现 ParkingSystem 类:
- ParkingSystem(int big, int medium, int small) 初始化 ParkingSystem类,三个参数分别对应每种停车位的数目。
- bool addCar(int carType) 检查是否有 carType 对应的停车位。
carType 有三种类型:大,中,小,分别用数字 1, 2 和 3 表示。一辆车只能停在 carType 对应尺寸的停车位中。如果没有空车位,请返回 false ,否则将该车停入车位并返回 true 。
本题很简单,但有很多拓展情况或高级解法需要学习,详情参考 解答
class ParkingSystem:
def __init__(self, big: int, medium: int, small: int):
self.size = [0, big, medium, small]
self.car = [0, big, medium, small]
self.dic = []
def addCar(self, carType: int) -> bool:
if self.car[carType] <= 0:
return False
self.car[carType] -= 1
return True
# 如果允许车离开停车位,该怎么做?
def removeCar(self, carType: int) -> bool:
if self.car[carType] >= self.size[carType]:
return False
self.car[carType] += 1
return True
# 如果允许小车停放在比它更大的停车位上,该怎么做?
def addSmallCar(self, carType: int) -> bool:
if sum(self.car[1:carType + 1]) <= 0:
return False
for i in range(carType ,0 ,-1):
if self.car[i] : # 小车优先小车位
self.car[i] -= 1
return True
# 如果给每个车增加 id,车离开车位的时候必须按照指定的顺序,比如先进先出,该怎么做?
# 我理解是要存一个,hashmap然后,弹出指定的车辆
def addCarList(self, carType: int) -> bool:
dic.append(carType)
return self.addCar(carType)
def removeCarList(self) -> bool:
if not dic : return False
return self.removeCar(dic.pop(0))
# 如果在并发场景的停入和离开车,如何保证结果正确?
# 是否可以参考concurrenthashmap分段锁来设计,蹲一个大佬
给你一个嵌套的整型列表。请你设计一个迭代器,使其能够遍历这个整型列表中的所有整数。
列表中的每一项或者为一个整数,或者是另一个列表。其中列表的元素也可能是整数或是其他列表。
今天的题意略难理解,需要我翻译一下,理解题意的朋友请跳过。
本题定义了一个类 NestedInteger ,这个类可以存储 int 或 List;所以称它是一个「嵌套列表」。类似于一棵多叉树,每个节点都可以有很多子节点。
它有三个方法:
- isInteger() ,判断当前存储的对象是否为 int;
- getInteger() , 如果当前存储的元素是 int 型的,那么返回当前的结果 int,否则调用会失败;
- getList() ,如果当前存储的元素是 List 型的,那么返回该 List,否则调用会失败。
而「扁平化嵌套列表迭代器」说的是,我们需要设计一个迭代器,这个迭代器是把「嵌套列表」铺平(拆包)成各个 int,然后每次调用 hasNext() 来判断是否有下一个整数,通过 next() 返回下一个整数。
注意迭代器是一种按照特定顺序对数据结构遍历的方式,它的调用方式是:
i, v = NestedIterator(nestedList), []
while i.hasNext():
v.append(i.next())
本文有两种主要的思路:
- 在构造函数中提前「扁平化」整个嵌套列表;(先预处理所有)
- 在调用 hasNext() 或者 next() 方法的时候扁平化当前的嵌套的子列表。(边用边处理)
详细解析可参考 解答
class NestedIterator {
private:
vector<int> vals;
vector<int>::iterator cur;
void dfs(const vector<NestedInteger> &nestedList) {
for (auto &nest : nestedList) {
if (nest.isInteger()) {
vals.push_back(nest.getInteger());
} else {
dfs(nest.getList());
}
}
}
public:
NestedIterator(vector<NestedInteger> &nestedList) {
dfs(nestedList);
cur = vals.begin();
}
int next() {
return *cur++;
}
bool hasNext() {
return cur != vals.end();
}
};
/**
* // This is the interface that allows for creating nested lists.
* // You should not implement it, or speculate about its implementation
* class NestedInteger {
* public:
* // Return true if this NestedInteger holds a single integer, rather than a nested list.
* bool isInteger() const;
*
* // Return the single integer that this NestedInteger holds, if it holds a single integer
* // The result is undefined if this NestedInteger holds a nested list
* int getInteger() const;
*
* // Return the nested list that this NestedInteger holds, if it holds a nested list
* // The result is undefined if this NestedInteger holds a single integer
* const vector &getList() const;
* };
*/
class NestedIterator {
private:
stack<NestedInteger> st;//注意:栈里存的是NestedInteger对象
public:
//存入栈中,在调用 hasNext() 或者 next() 方法的时候再拆开当前嵌套的子列表。
NestedIterator(vector<NestedInteger> &nestedList) {
for(int i = nestedList.size() - 1; i >= 0; i--)
st.push(nestedList[i]);
}
int next() {
NestedInteger cur = st.top();//里面都是按NestedInteger类存储的
st.pop();
return cur.getInteger();//调用接口返回整型
}
bool hasNext() {
while(!st.empty()){
NestedInteger cur = st.top();
if(cur.isInteger()){
return true;
}
st.pop();//当前NestedInteger对象不是整数,是list,就是从栈中拿出,再次逆序解析成整数存入
for(int i = cur.getList().size() - 1; i >= 0; i--){
st.push(cur.getList()[i]);
}
}
return false;
}
};
给定一个整数序列:a1, a2, …, an,一个132模式的子序列 ai, aj, ak 被定义为:当 i < j < k 时,ai < ak < aj。设计一个算法,当给定有 n 个数字的序列时,验证这个序列中是否含有132模式的子序列。
注意:n 的值小于15000。
1. 直接三个下标暴力枚举
题目只让我们找出序列中是否存在,那么我们可以在合理的i
class Solution {
public:
bool find132pattern(vector<int>& nums) {
int n = nums.size();
if(n < 3) return false;
int i = 0, j = 0, k = 0;//对应1,3,2模式
//暴力枚举模式1(的起点下标i)
while(i < n){
while(i < n - 2 && nums[i] >= nums[i+1])//1->3:滤掉连续的递增
i++;
j = i + 1;
while(j < n - 1 && nums[j] <= nums[j+1])//3->2:滤掉连续递减
j++;
//枚举右侧2的所有可能值
for(k = j + 1; k < n; k++){
if(nums[k] < nums[j] && nums[k] > nums[i])
return true;
}
i = j + 1;//更新模式1的起点i
}
return false;
}
};
2. 单调栈
- 满足132模式,我们可以保证最大的3和次大的2 是 数组里尽可能大的值,那么留给最小的1可选空间就足够大了,如果这样都找不到,肯定就不满足了。
- q且下标要满足i
- 用一个栈保存最大值,second代表最小值,从后往前遍历,遇到更大的就依次更新掉 最大值 和 次大值;
- 若过程中,左边遇到小于他俩的 就说明找到满足条件的数了,返回ture;遍历完都没遇到就返回false;
class Solution {
public:
//枚举最小(保证3,2尽量大):用栈存放最大的元素,次大的元素用second存放,遍历方式从后往前。若找到比second小的元素则说明存在132模式。
bool find132pattern(vector<int>& nums) {
int n = nums.size();
if(n < 3) return false;
stack<int> maxStk;//维护一个栈,存最大值
int second = INT_MIN;//存次大值
//逆序遍历
for(int i = n - 1; i >= 0; i--){
if(nums[i] < second) return true;
//找到更大的就更新最大值和次大值
while(!maxStk.empty() && nums[i] > maxStk.top()){
second = maxStk.top();
maxStk.pop();
}
maxStk.push(nums[i]);
}
return false;
}
};