C++で異なる整数がちょうどK個含まれる部分配列を数える方法
正の整数からなる配列Aがあるとします。連続する要素からなる部分配列(サブ配列)の中で、含まれる異なる整数の個数がちょうどK個であるものを「良い部分配列」と呼ぶことにします。例えば、配列 [1,2,3,1,2] には 1、2、3 という3つの異なる整数が含まれています。この記事では、配列Aに存在する「良い部分配列」の総数を求めるアルゴリズムを解説します。
問題の例
入力が [1,2,3,1,4]、K = 3 の場合を考えてみましょう。このとき出力は 4 になります。これは、異なる整数がちょうど3つ含まれる部分配列として、以下の4つが存在するためです。
- [1,2,3]
- [1,2,3,1]
- [2,3,1]
- [3,1,4]
解法のアプローチ:スライディングウィンドウ
この問題は「あと何個まで許容できるか」という考え方(At Most テクニック)を使うことで効率的に解けます。まず、異なる整数が最大k個まで含まれる部分配列の個数を数える関数 atMost(a, k) を定義します。
atMost() 関数の手順
- 配列
aと変数kを受け取ります。 - 左端ポインタ
j := 0、答えans := 0、各整数の出現回数を記録するマップmを用意します。 - 右端ポインタ
iを0から順に進めます。 - 新しい要素
a[i]を追加し、まだ登録されていなければkを減らします。 k < 0になったら(異なる整数が多すぎる場合)、条件を満たすまで左端jを進めて窓を縮小します。要素が出現しなくなったらkを増やします。- 各ステップで
(i - j + 1)個の部分配列が成立するので、これをansに加算します。
最終的な答えは次の式で求められます。
atMost(a, k) - atMost(a, k - 1)
「最大k個」の場合から「最大k-1個」の場合を引くことで、ちょうどk個の異なる整数を含む部分配列だけを取り出せます。計算量はO(n)となり、非常に効率的です。
C++実装例
#include <bits/stdc++.h>
using namespace std;
class Solution {
public:
int subarraysWithKDistinct(vector<int>& a, int k) {
return atMost(a, k) - atMost(a, k - 1);
}
int atMost(vector <int>& a, int k){
set <int> current;
int j = 0;
int ans = 0;
int n = a.size();
unordered_map <int, int> m;
for(int i = 0; i < a.size(); i++){
if(!m[a[i]]++) k--;
while(k < 0){
if(!--m[a[j]])
k++;
j++;
}
int x = ((i - j) + 1);
ans += x;
}
return ans;
}
};
main(){
Solution ob;
vector<int> v = {1,2,3,1,4};
cout << (ob.subarraysWithKDistinct(v, 3));
}
入力
{1,2,3,1,4}, 3
出力
4
この実装では、各要素が高々1回追加・削除されるため、全体の計算量はO(n)、空間計算量もO(n)で抑えられます。スライディングウィンドウと引き算テクニックの組み合わせは、「ちょうどK個」という条件を持つ部分配列問題における定番パターンなので、ぜひ覚えておきましょう。
-
C++で合計が0となるすべての部分配列を出力する方法
この記事では、整数値の配列が与えられたときに、要素の合計が0になるすべての部分配列(連続した要素の並び)を見つけ出し、それらを出力する方法をC++で解説します。 問題の概要 まず、具体例を使って問題を理解しましょう。 入力: arr[] = {-5, 0, 2, 3, -3, 4, -1} この配列の場合、合計が0になる部分配列は以下の通りです。 {0} … インデックス1のみ {-5, 0, 2, 3} … インデックス0〜3 {3, -3} … インデックス3〜4 {-3, 4, -1} … インデックス4〜6 {-5, 0, 2, 3, -3, 4, -1} … インデックス0〜6(配
-
C++のifstreamを使ってテキストファイルから整数を読み取る方法
C++のifstreamクラスを使うと、テキストファイルから整数を簡単に読み取ることができます。この記事では、ifstreamを使ってテキストファイル内の整数を読み込み、配列に格納して表示する方法を、サンプルコードとともに解説します。 サンプルコード #include <fstream> #include <iostream> using namespace std; int main() { // 配列のサイズを定義 int arr[30]; ifstream is(a.txt); int cnt = 0; int x;