C++で有効な部分配列の個数を求めるアルゴリズム
整数の配列 A が与えられたとき、「部分配列の左端の要素が、その部分配列内の他のすべての要素以下である」という条件を満たす、空でない連続した部分配列の個数を求めます。
例えば、入力が [1,4,2,5,3] の場合、出力は 11 になります。条件を満たす部分配列は次の 11 個です。
[1], [4], [2], [5], [3], [1,4], [2,5], [1,4,2], [2,5,3], [1,4,2,5], [1,4,2,5,3]
解法のアプローチ:単調スタック
この問題は「単調スタック(増加スタック)」を使うことで効率的に解けます。各要素を処理する際、スタックの先頭にある自分より大きい要素をすべて取り除いてから、現在の要素をプッシュします。このときのスタックのサイズが、「現在の位置で終わる有効な部分配列の個数」に対応します。
手順
- 答えを格納する変数 ret を 0 で初期化します。
- n を配列 nums のサイズとします。
- スタック st を用意します。
- i を 0 から nums のサイズまで繰り返します。
- x := nums[i] とします。
- スタックが空でなく、かつ x がスタックの先頭要素より小さい間、スタックから要素を取り除きます(pop)。
- x をスタックに挿入します(push)。
- ret にスタックのサイズを加算します。
- 最後に ret を返します。
なぜこれで正しく求まるのか
スタックには「現在の要素より大きくない値」だけが残ります。つまり、スタック内の各要素は、そこを始点とし現在の位置を終点とする部分配列の左端として条件を満たしています。したがって、各時点でのスタックのサイズを合計すれば、すべての有効な部分配列を重複なく数えることができます。
C++での実装例
理解を深めるために、以下の実装を見てみましょう。
#include <bits/stdc++.h>
using namespace std;
class Solution {
public:
int validSubarrays(vector<int>& nums) {
int ret = 0;
int n = nums.size();
stack <int> st;
for(int i = 0; i < nums.size(); i++){
int x = nums[i];
while(!st.empty() && x < st.top()) st.pop();
st.push(x);
ret += st.size();
}
return ret;
}
};
main(){
Solution ob;
vector<int> v = {1,4,2,5,3};
cout << (ob.validSubarrays(v));
}
入力
{1,4,2,5,3}
出力
11
計算量
- 時間計算量: O(n)。各要素は最大でも 1 回プッシュされ、1 回しかポップされないためです。
- 空間計算量: O(n)。最悪ケース(昇順に並んだ配列など)では、スタックにすべての要素が保持されます。
-
C++で五胞体数(ペンタトープ数)を求める方法
五胞体数とは? 五胞体数(ペンタトープ数)は、パスカルの三角形の第5の対角線上に現れる数列として知られています。この数列を定義するには、パスカルの三角形に少なくとも5つの数が必要となるため、数列の最初の数はパスカルの三角形の第4行である 1 4 6 4 1 から始まります。 本チュートリアルでは、n番目の五胞体数を求める方法を解説します。まずは具体的な例を見てみましょう。 入力 : 1出力 : 1入力 : 4出力 : 35 以下の図から出力を確認できます。 この問題は数列に関するものなので、解法ではまず数列のパターンを見つけることから始めます。 解法のアプローチ このプログラムでは、数列の
-
C++で数独の有効性を判定するアルゴリズムを解説
9×9の行列で表される数独(Sudoku)が与えられたとします。この課題の目的は、与えられた数独の配置が有効かどうかを判定することです。一般的な数独の盤面は次のようになります。数独のルール各行には1〜9の範囲の数字が入る各列には1〜9の範囲の数字が入る各3×3のブロックには重複のない数字が入る同じ行に同じ数字が現れることはできない同じ列に同じ数字が現れることはできない入出力の例入力例:sudoku[]= [[3,5,.,.,2,.,.,.,.] ,[7,.,.,1,6,5,.,.,.] ,[.,9,8,.,.,.,.,6,.] ,[8,.,.,.,6,.,.,.