スタックとキューの概要
2 つは C++ ではスタックとキューですが、C++ では、この 2 つはシーケンシャル ストレージやチェーン ストレージなどの従来の方法では実装されず、現在は許可メソッドを使用して実装されています。
これらはすべてコンテナアダプタで実装されており、開発者は従来の書き方と同じように実装する必要はなく、コンテナを再利用することで直接実装できると考えています。 、どちらも deque コンテナーに基づいて実装されます。
アダプターの本質は実際には多重化です。
この 2 つによって実装される関数は、実際にはデータ構造で学んだものと何ら変わりはなく、構造も同じです。
どちらもコンテナ アダプタであり、カジュアル トラバーサルをサポートしておらず、挿入と削除に関する独自のルールがあるため、これら 2 つのコンテナはイテレータをサポートしていません。
スタックの公式ドキュメント: stack - C++ リファレンス (cplusplus.com)
キューの公式ドキュメント:キュー - C++ リファレンス (cplusplus.com)
スタックとキューの例
STL のクラスは使用法が似ており、この 2 つの使用法は非常に簡単なので、スタック クラスとキュー クラスの使用法を理解するには、いくつかの例を直接使用してください。
push
、pop
、 のtop
操作をサポートし、一定時間で最小の要素を取得できるスタックを設計します 。
実装 MinStack
クラス:
MinStack()
スタックオブジェクトを初期化します。void push(int val)
要素 val をスタックにプッシュします。void pop()
スタックの最上位にある要素を削除します。int top()
スタックの先頭にある要素を取得します。int getMin()
スタック内の最小の要素を取得します。
問題は、geiMin() 関数が O(1) の時間計算量を必要とすることです。したがって、従来の方法では解決できず、スタックはトラバーサルをサポートしていません。
まず考えられるのは、MinStackクラスにスタックコンテナがあり、そのスタックコンテナに最小値を格納するためのMinメンバがあり、Minより小さい値をプッシュするとMinが格納されるということです。が更新されます。これには多くの問題があり、Pop が必要なときに Min を時間内に更新することが困難です。
したがって、上記の方法は実行できません。
この問題を解決するために、2 つのスタックを使用してスペースと時間を交換します。1 つのスタックにはプッシュされた要素、つまりデータを格納するスタックが格納され、もう 1 つのスタックには最初のスタックの最小値が格納されますが、格納方法は若干異なります。
上図にあるように、_st スタックはデータを格納するスタックであり、上の例ではプッシュ順序は (3 4 5 1) になります。_st がスタック 3 にプッシュされると、この時点では 3 が _st スタックの最小値であり、その後 _minst に 3 をプッシュします。その後、_st が 4 にプッシュされると、この時点では _st スタックの最小値はまだ 3 です。 minst スタックは引き続き 3 をプッシュします。次の 5 は同じです。_st がスタック 1 にプッシュされると、1 が _st スタックの最小値になり、_minst スタックは 1 にプッシュされます。
上記の方法を使用して、_st スタックへのプッシュがないとき、そのときの _st スタックの最小値を記録します。
上記のコードは最適化することもできます。上記の _minst スタックには複数の 3 が保存されています。実際、これらの繰り返し値は削除する必要はなく、より小さい要素をスタックにプッシュするだけで済みます。以下に示すように:
注: 上記の最適化されたバージョンでは、_st のスタックにプッシュされた要素が _minst の最上位の要素と等しい場合、それもスタックにプッシュされます。
コンストラクターを記述する必要はありませんが、空のコンストラクターを記述することも可能です。コンストラクターが実装されている場合、コンストラクター内に操作がなくても、コンパイラーは初期化リストを呼び出し、初期化リストのデフォルトは次のとおりです。組み込み型を処理しません。カスタム型の場合、このカスタム型のコンストラクターが呼び出されます。したがって、このコンストラクターを記述しなくても問題ありませんが、操作を行わずに空のコンストラクターを記述しても問題ありません。
同様に、デストラクタも記述する必要はありませんが、ここでは、クラス minstack と 2 つのスタック メンバーを処理するためにコンパイラが自動的に生成しますが、スタックには処理する独自のデストラクタがあるため、このクラスでは、デストラクタは記述されません。
コード:
class MinStack {
public:
// 可写可不写
MinStack() {
}
void push(int val) {
_st.push(val);
if(_minst.empty() || _minst.top() >= val)
{
_minst.push(val);
}
}
void pop() {
if(_minst.top() == _st.top())
{
_minst.pop();
}
_st.pop();
}
int top() {
return _st.top();
}
int getMin() {
return _minst.top();
}
private:
stack<int> _st;
stack<int> _minst;
};
2 つの整数シーケンスを入力します。最初のシーケンスはスタックのプッシュ順序を表し、2 番目のシーケンスがスタックのポップ順序であるかどうかを判断してください。スタックにプッシュされたすべての数値が等しくないものと仮定します。例えば、シーケンス1、2、3、4、5は、あるスタックのプッシュシーケンスであり、シーケンス4、5、3、2、1は、プッシュシーケンスに対応するポップシーケンスであるが、4、3、 5、1、2 プッシュシーケンスのポップシーケンスにはできません。
1. 0<=pushV.length == PopV.length <=1000
2. -1000<=pushV[i]<=1000
3. すべてのpushVの数が異なります
押す順番と弾く順番が同じかどうかをシミュレーションして判定することができます。次の例を分析してください。
まずこの兄弟例は条件を満たしているので、積み順の最初から判断して徐々にスタックに押し込んでいきます。1と3が等しくない場合はスタックにプッシュ 2と3が等しくない場合はスタックにプッシュを続ける 3と3が等しい場合は3をプッシュした後にスタックをポップし、ポップの順番を判断する2、2、2 は等しいので、スタックのポップを続けます ; 次に、積み上げシーケンスの 5 が等しくないと判断し、スタックへのプッシュを続けます; 積み上げシーケンスの 5 が積み上げシーケンスの 5 に等しい後は、スタックへのプッシュを続けます、ポップされる、その後 4 と 4 が等しい、そしてポップされる、最後にプッシュとポップが完了します。
条件を満たさない例であれば、上記の処理によれば、スタッキング完了後、ポッピングが完了していない必要があり、条件を満たさないことになる。
コード:
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param pushV int整型vector
* @param popV int整型vector
* @return bool布尔型
*/
bool IsPopOrder(vector<int>& pushV, vector<int>& popV) {
stack<int> st;
int pushi = 0, popi = 0;
while(pushi < pushV.size())
{
st.push(pushV[pushi++]);
if(st.top() != popV[popi])
{
continue;
}
else
{
while(!st.empty() && st.top() == popV[popi])
{
st.pop();
++popi;
}
}
}
return st.empty();
}
};
逆ポーランド記法に従って 表現された算術式をtokens
表す 文字列の配列が与えられます 。
この式を計算してみてください。式の値を表す整数を返します。
私たちが通常使用する式はすべて、1 + 2 * 3 のような中置式です。中置式は演算子がオペランドの真ん中にあることを意味しますが、これはコンピュータが優先順位を認識するのに役立ちません。認識するのは使用される後置表現であり、上記の逆ポーランド語表現です。
オペランドの順序はそのままで、演算子の順序は優先順位に従って配置されるというもので、接尾辞式として書くと 2 + 1 * 3 のように、オペランドが先、演算子が後になります。 1 3 * +
上記のトピックでは、指定された接尾辞式の結果を計算することのみが必要であり、中接辞を接尾辞に変換する必要はなく、接尾辞式の結果を計算するだけで非常に簡単です。
スタックを使用して結果を保存できます。まず、すべてのオペランドをスタックにプッシュし、次にスタックの先頭にある 2 つの要素を操作のために取得し、結果をスタックにプッシュします。これにより、後置式の結果を計算できるようになります。
コード:
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<int> st;
for(auto& ch : tokens)
{
if(ch == "+" || ch == "-" || ch == "*" || ch == "/")
{
int right = st.top();
st.pop();
int left = st.top();
st.pop();
switch(ch[0])
{
case '+':
st.push(left + right);
break;
case '-':
st.push(left - right);
break;
case '*':
st.push(left * right);
break;
case '/':
st.push(left / right);
break;
}
}
else
{
st.push(stoi(ch));
}
}
return st.top();
}
};
インフィックスからサフィックスへのアイデアの簡単な説明は次のとおりです。引き続きスタックを使用し、式を 1 回トラバースし、オペランドに遭遇した場合は直接出力し、演算子に遭遇した場合はそれをスタックにプッシュします。
オペレータ スタック操作には、次の 2 つのケースがあります。
- スタックが空であるか、現在のオペレーターの優先順位がスタックの最上位よりも高い場合、そのオペレーターはスタックにプッシュされ続けます。
- スタックが空でない場合、または現在の演算子の優先順位がスタックの先頭の優先順位以下である場合は、演算子をスタックの先頭に出力します。
次に、これには演算子の優先順位の問題が関係しますが、対応するマッピング関係を確立するためにマップを使用すれば解決できます。Mapを使わない場合はif elseやswitchを使って激しく実装することも可能です。
バイナリ ツリーのルート ノードを指定すると 、root
そのノード値の レベル順の走査を返します 。(つまり、レイヤーごとに、左から右にすべてのノードを訪問します)。
このトピックは通常の層シーケンス走査とは異なります. また、ツリーの各層のノード値を 2 次元配列の各層に割り当てる必要があります. したがって、単に出力するだけでなく、現在プリントされたノード、それがそのレイヤーです。
方法 1: 2 つのキューを使用できます。1 つのキューはレイヤー シーケンスのトラバーサル、ツリー ノードのエンキューおよびデキューに使用され、もう 1 つは現在キューに入れられているノードが属するレイヤーの数を格納するために使用されます。以下に示すように;
上記の二重キューは実現可能ですが、最適解ではありません。
さらに良いのは、levelsize 変数を使用して各層の出力ノードを制御することです。
具体的な方法は、ある層の最初のノードの前に、最初にこの層のノードの数を計算して levelsize に保存し、次にこの層のノードのノードを出力するときに levelsize-- 、levelsize が減少するまでです。 0にすると、この時点でこの層のノードが出力されたことを意味します。以下の図に示すように (3 番目のレイヤーの出力前):
このようにして各層の出力を制御できますが、問題は各層の数値をどのように計算するかです。
実際、チームへの参加とチームからの離脱のプロセスでは、ツリーの走査が完了するまでが最後までのサイクルであり、実際には、このループの層にループの層を追加し、この層にループを追加することができます。ループの数は、この層の結果を出力するために使用されます。ポイント、内側のループが終了すると、現在の層のノードはキューから削除されますが、この層のすべての子がキューに入れられ、キュー内のすべてのノードがキューに追加されます。今回は前の階層の親ノードであり、点で与えられた子ノード、つまりこの時点でキューに格納されている全てのノードが次の階層のノード全てとなる。
したがって、現在のキュー内の要素の数を直接見つけて、それを levelsize に割り当てることができます。
コード:
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> vv;
queue<TreeNode*> q;
int levelsize = 0;
if(root)
{
q.push(root);
levelsize = 1;
}
while(!q.empty())
{
vector<int> v;
for(int i = 0; i < levelsize;i++)
{
TreeNode* front = q.front();
q.pop();
v.push_back(front->val);
if(front->left)
{
q.push(front->left);
}
if(front->right)
{
q.push(front->right);
}
}
vv.push_back(v);
levelsize = q.size();
}
return vv;
}
};
バイナリ ツリーのルート ノードを指定すると 、root
そのノード値の レベル順の走査を返します 。(つまり、レイヤーごとに、左から右にすべてのノードを訪問します)。
上記の方法を使用して、vv ベクトルの格納に使用されるクラスを予約します。