C++で全要素が等しい最大の正方形部分行列を求めるアルゴリズム
問題概要
この問題では、N×N の行列 mat[] が与えられ、すべての要素が等しい最大の正方形部分行列を見つけることが課題となります。
つまり、与えられた行列の中から、全要素が同じ値で構成される正方形部分行列のうち、最大のサイズを求める必要があります。
問題を理解するための具体例
入力: mat[][] = {{1, 2, 1}, {1, 2, 2}, {2, 2, 2}}
出力: 2説明:
a11, a12, a21, a22 の位置にある要素は 2×2 の正方形を形成し、すべて同じ値(2)で構成されています。
解法アプローチ
単純な解法(総当たり): 行列のすべての要素を走査し、考えられるすべての部分行列について「全要素が同じかどうか」を確認する方法です。この場合、走査自体に O(n3) の時間計算量がかかり、さらに各部分行列の検証にも O(n2) を要するため、大規模な行列には不向きです。
効率的な解法(動的計画法): 動的計画法(DP)を用いることで、より効率的にこの問題を解けます。各マス (i, j) に対して「その位置を右下とする、全要素が等しい正方形部分行列の最大サイズ」を DP 表に格納していきます。その際、左・上・左上の隣接セルの値を参照しながら、条件を満たす最大の正方形を求めます。DP 表の各セルの値は、次のように定式化できます。
セル (i, j) の要素が、上・左・左上のいずれとも同じ値である場合、DP の値を次のように更新します。
DP[i][j] = min(DP[i-1][j], DP[i][j-1], DP[i-1][j-1]) + 1
隣接する要素が異なる場合は、そのセル自身から新たな正方形が始まるとみなし、
DP[i][j] = 1
とします。
アルゴリズムのポイント
- DP[i][j] は、セル (i, j) を右下の角とする「全要素が等しい正方形部分行列」の最大辺の長さを表します。
- 1行目と1列目のセルは、それ自身しか正方形を構成できないため、DP の値は 1 で初期化されます。
- DP 表をすべて埋め終えたあとの最大値が答えとなります。
- 時間計算量・空間計算量はともに O(n2) で、総当たり法よりも大幅に効率的です。
C++による実装例
以下は、この解法の動作を示す C++ プログラムです。
#include<bits/stdc++.h>
#define n 4
#define m 4
using namespace std;
int findmaxSqMatSize(int mat[][m]){
int DP[n][m];
memset(DP, 0, sizeof(DP));
int maxSqMatSize = 0;
for (int i = 0 ; i < n ; i++){
for (int j = 0 ; j < m ; j++){
if (i == 0 || j == 0)
DP[i][j] = 1;
else{
if (mat[i][j] == mat[i-1][j] && mat[i][j] == mat[i][j-1] && mat[i][j] == mat[i-1][j-1] )
DP[i][j] = min(min(DP[i-1][j], DP[i][j-1]), DP[i-1][j-1] ) + 1;
else DP[i][j] = 1;
}
maxSqMatSize = max(maxSqMatSize, DP[i][j]);
}
}
return maxSqMatSize;
}
int main(){
int mat[n][m] = { {2, 1, 4, 3},
{5, 1, 1, 7},
{1, 1, 1, 4},
{9, 4, 6, 0}};
cout<<"The maximum square sub-matrix with all equal elements is "<<findmaxSqMatSize(mat);
return 0;
}実行結果
The maximum square sub-matrix with all equal elements is 2
(日本語訳:「全要素が等しい最大の正方形部分行列のサイズは 2 です」。この入力では、値 1 で構成される 2×2 の部分行列が最大となります。)
-
C++で最も深いノードをすべて含む最小の部分木を求める方法
問題の概要 ルートを頂点とする二分木が与えられます。各ノードの「深さ」とは、そのノードからルートまでの最短距離のことで、木全体の中で最大の深さを持つノードを「最も深いノード」と呼びます。また、あるノードの「部分木」とは、そのノード自身とそのすべての子孫からなる集合のことです。 この問題では、すべての最も深いノードをその部分木に含むようなノード、すなわち最小の共通部分木の根となるノードを求めます。 たとえば、次のような二分木が与えられたとします。 このとき、求めるべき最小の部分木は次のようになります。 解法のアプローチ この問題は、再帰的な深さ優先探索(DFS)を使うことで効率的に解けます。
-
C++ですべての配列要素を等しくするために必要な最小操作回数を求める方法
問題文 n 個の正の整数からなる配列が与えられます。すべての要素を等しくするために必要な最小の操作回数を求めてください。1 回の操作では、配列内の任意の要素に対して、加算・乗算・減算・除算のいずれかを行うことができます。 例 入力配列が {1, 2, 3, 4} の場合、すべての要素を等しくするには最小で 3 回の操作が必要です。たとえば、要素 1 に対して 3 回の加算を行えば、すべての要素を 4 に揃えることができます。 アルゴリズム 最も出現回数(頻度)が多い要素を選びます。これを「x」と呼びます。 同じ値の要素がすでに x 個存在するため、残りの n − x 個の要素に対して操作を