C++で変更回数を最小限に抑えて整数配列を狭義単調増加に変換する方法
問題の概要
このチュートリアルでは、変更回数を最小限に抑えて、整数配列を狭義単調増加(厳密に増加する順序)の配列に変換するプログラムについて解説します。
具体的には、整数配列が1つ与えられます。私たちのタスクは、要素の書き換えをできるだけ少ない回数にとどめながら、配列全体を狭義単調増加の順序にすることです。
アルゴリズムの考え方
この問題は、最長増加部分列(LIS: Longest Increasing Subsequence)を求める動的計画法を応用することで解けます。鍵となるのは、「変更せずにそのまま残せる要素」を見抜くための条件です。
インデックス j と i(j < i)にある2つの要素をどちらも変更せずに残すためには、次の2つの条件を満たす必要があります。
- arr[i] > arr[j] … 値が厳密に増加していること
- (i - j) <= (arr[i] - arr[j]) … インデックスの差が値の差以下であること
2つ目の条件は、間に挟まれた要素を整数に書き換えて狭義単調増加にするために不可欠です。インデックスが1つ進むごとに、値は少なくとも1以上増える必要があるためです。
この条件を満たす要素をつなげた最長の列の長さをLISの要領で求め、配列の長さ n からその長さを引けば、必要な最小の変更回数が得られます。
C++による実装例
#include <bits/stdc++.h>
using namespace std;
// 必要な変更回数を計算する
int remove_min(int arr[], int n){
int LIS[n], len = 0;
for (int i = 0; i < n; i++)
LIS[i] = 1;
for (int i = 1; i < n; i++) {
for (int j = 0; j < i; j++) {
if (arr[i] > arr[j] && (i-j)<=(arr[i]-arr[j])){
LIS[i] = max(LIS[i], LIS[j] + 1);
}
}
len = max(len, LIS[i]);
}
// 必要な変更回数を返す
return n - len;
}
int main(){
int arr[] = { 1, 2, 6, 5, 4 };
int n = sizeof(arr) / sizeof(arr[0]);
cout << remove_min(arr, n);
return 0;
}出力
2
コードの解説
上記の例では、配列 { 1, 2, 6, 5, 4 } が入力として与えられています。条件を満たして変更せずに残せる要素は、たとえば「1, 2, 6」(インデックス 0, 1, 2)の3つです。したがって、残りの「5」と「4」の2要素を書き換えればよく、出力は 2 となります。
すべての要素ペアを二重ループで調べているため、このアルゴリズムの計算量は O(n²) です。要素数が多い場合は、LISを二分探索で求める O(n log n) の手法への拡張も検討するとよいでしょう。
-
C++で絶対差の合計が最小となる配列要素を求める方法
このプログラムは、重複しない要素からなる配列が与えられたときに、各要素の絶対差の合計が最小となる値を求めるものです。この概念をより深く理解するために、まず必要な基礎知識をおさらいしましょう。配列(Array)とは、同じデータ型の要素を格納するためのコンテナです。配列の長さは事前に定義しておく必要があります。絶対差(Absolute Difference)とは、2つの数値の差の絶対値のことです。つまり、差は常に正の値となり、負の値は正の値に変換されます。各要素について最小絶対差を求め、その合計を計算します。最小絶対差の公式は次のとおりです。Minimum Absolute Difference
-
【C++】配列を互いに素な配列に変換するための最小挿入回数を求める方法
問題の概要 今回は、与えられた配列を互いに素な配列(コプライム配列)に変換するために必要な最小の挿入回数を求める、興味深い問題を取り上げます。互いに素な配列とは、隣り合う任意の2つの要素の最大公約数(GCD)が必ず1になる配列のことです。この記事では、必要な挿入回数に加えて、変換後の配列そのものも出力します。 例として、{5, 10, 20} という配列を考えてみましょう。この配列は隣接要素同士のGCDが5や10となるため、互いに素な配列ではありません。しかし、5と10の間、そして10と20の間にそれぞれ「1」を挿入すれば、{5, 1, 10, 1, 20} となり、すべての隣接ペアのGCD