C++で配列の要素をすべて一意にするための最小増加操作回数を求める方法
問題の概要
整数の配列 A が与えられます。ここでいう「1回の操作」とは、任意の A[i] を選び、その値を 1 だけ増やすことです。この操作を繰り返して、配列 A のすべての値を一意(重複なし)にするために必要な最小の操作回数を求めます。
例えば、入力が [3,2,1,2,1,7] の場合、出力は 6 になります。6 回の操作を行うと、配列は [3,4,1,2,5,7] となり、すべての値が異なる状態にできます。一方、5 回以下の操作ではすべての値を一意にすることができないことが示せます。
解法のアプローチ
この問題は、ソートと貪欲法(グリーディ法)を組み合わせることで効率的に解くことができます。手順は以下のとおりです。
- 答えを格納する変数 ret を 0 で初期化します。
- 配列 A を昇順にソートします。
- i を 1 から配列 A のサイズ - 1 までループします。
- もし A[i] が A[i-1] 以下であれば、ret に (A[i-1] + 1) - A[i] を加算し、A[i] を A[i-1] + 1 に更新します。
- 最後に ret を返します。
この方法のポイントは、ソート済みの配列に対して隣接する要素同士を比較することで、重複を検出できる点にあります。A[i] が直前の要素以下の場合、A[i] を「直前の要素 + 1」まで増やす必要があり、その増分がそのまま操作回数として ret に加算されます。ソートによって配列が昇順に並んでいるため、この処理を左から右へ一度行うだけで、全体の最小操作回数が求まります。
実装例
より理解を深めるために、以下の C++ 実装を見てみましょう。
#include <bits/stdc++.h>
using namespace std;
class Solution {
public:
int minIncrementForUnique(vector<int>& A) {
int ret = 0;
sort(A.begin(), A.end());
for(int i = 1; i < A.size(); i++){
if(A[i] <= A[i - 1]){
ret += (A[i - 1] + 1) - A[i];
A[i] = A[i - 1] + 1;
}
}
return ret;
}
};
main(){
vector<int> v1 = {3,2,1,2,1,7};
Solution ob;
cout << (ob.minIncrementForUnique(v1));
}
入力
[3,2,1,2,1,7]
出力
6
計算量の分析
- 時間計算量: O(n log n) — 配列のソートが支配的であり、その後の走査は O(n) で完了します。
- 空間計算量: O(1) — ソートをインプレースで行う場合、追加のメモリは定数個の変数のみで済みます。
このように、ソートと隣接要素の比較というシンプルな発想により、すべての要素を一意にするための最小操作回数を効率よく求めることができます。
-
配列のGCDをkの倍数にするための最小操作回数をC++で求める方法
問題の概要配列 arr と整数 k が与えられたとき、配列全体の GCD(最大公約数)を k の倍数に一致させるために必要な最小の操作回数を求めます。ここでいう「操作」とは、任意の要素の値を 1 増やす、または 1 減らすことを指します。例として、配列が {4, 5, 6}、k = 5 の場合を考えてみます。4 を 1 増やして 5 にし、6 を 1 減らして 5 にすれば、すべての要素が 5 の倍数になり、GCD も 5 となります。このとき必要な操作回数は 2 回です。アルゴリズムの手順配列の GCD を k の倍数にするには、すべての要素を k の倍数に揃える必要があります。以下の手順に
-
【C++】配列を互いに素な配列に変換するための最小挿入回数を求める方法
問題の概要 今回は、与えられた配列を互いに素な配列(コプライム配列)に変換するために必要な最小の挿入回数を求める、興味深い問題を取り上げます。互いに素な配列とは、隣り合う任意の2つの要素の最大公約数(GCD)が必ず1になる配列のことです。この記事では、必要な挿入回数に加えて、変換後の配列そのものも出力します。 例として、{5, 10, 20} という配列を考えてみましょう。この配列は隣接要素同士のGCDが5や10となるため、互いに素な配列ではありません。しかし、5と10の間、そして10と20の間にそれぞれ「1」を挿入すれば、{5, 1, 10, 1, 20} となり、すべての隣接ペアのGCD