C++
 Computer >> コンピューター >  >> プログラミング >> C++

C++で最も近い約数のペアを効率的に求めるアルゴリズム

問題概要

整数 num が与えられたとき、積が num + 1 または num + 2 と等しくなるような2つの整数のうち、絶対差(2つの値の差の絶対値)が最小になるペアを求めます。返す順序は問われません。

例えば、入力が 8 の場合を考えてみましょう。
num + 1 = 9 のとき、最も近い約数のペアは [3, 3] です。
num + 2 = 10 のとき、最も近い約数のペアは [2, 5] です。
両者を比較すると、差がより小さいのは [3, 3] なので、これが出力となります。

解法のアプローチ

この問題は「約数列挙」と「差の比較」を組み合わせることで解けます。手順は以下の通りです。

  • getDiv() というメソッドを定義します。引数として整数 x を受け取ります。
  • diff を無限大(INT_MAX)で初期化し、サイズ2の配列 ret を作成します。
  • i = 1 から始めて i * i <= x の間、i を1ずつ増やしながらループします。
    • xi で割り切れる場合:
      • a = ib = x / i とする
      • newDiff = |a - b| を計算する
      • newDiff < diff であれば、diff = newDiff とし、ret[0] = aret[1] = b を更新する
  • ret を返します。
  • メイン処理では op1 = getDiv(num + 1)op2 = getDiv(num + 2) をそれぞれ求めます。
  • |op1[0] - op1[1]| <= |op2[0] - op2[1]| であれば op1 を、そうでなければ op2 を返します。

計算量について

この手法では、平方根までの数だけを試すため、各 getDiv() の呼び出しは O(√x) 時間で完了します。全体としても非常に効率的であり、大きな num に対しても高速に動作します。

C++による実装例

以下の実装を見ると、理解がより深まるでしょう。

#include <bits/stdc++.h>
using namespace std;
void print_vector(vector<auto> v){
   cout << "[";
   for(int i = 0; i<v.size(); i++){
      cout << v[i] << ", ";
   }
   cout << "]"<<endl;
}
class Solution {
public:
   vector <int> getDiv(int x){
      int diff = INT_MAX;
      vector <int> ret(2);
      for(int i = 1; i * i <= x; i++){
         if(x % i == 0){
            int a = i;
            int b = x / i;
            int newDiff = abs(a - b);
            if(newDiff < diff){
               diff = newDiff;
               ret[0] = a;
               ret[1] = b;
            }
         }
      }
      return ret;
   }
   vector<int> closestDivisors(int num) {
      vector <int> op1 = getDiv(num + 1);
      vector <int> op2 = getDiv(num + 2);
      return abs(op1[0] - op1[1]) <= abs(op2[0] - op2[1]) ? op1 : op2;
   }
};
main(){
   Solution ob;
   print_vector(ob.closestDivisors(8));
}

入力

8

出力

[3,3]

まとめ

この問題のポイントは、num + 1num + 2 のどちらか一方は必ず偶数になるため、必ず有効な約数のペアが存在することです。約数を効率的に列挙し、差が最小のペアを選ぶことで、シンプルかつ高速に解答できます。

  1. C++で範囲加算を効率的に解く方法 ― 差分配列(いもす法)の活用

    問題概要 サイズnの整数配列が与えられ、すべての要素が0で初期化されているとします。さらに値kが与えられ、k回の更新操作を行います。各操作は [startIndex, endIndex, inc] という3つ組で表され、部分配列 A[startIndex ... endIndex](startIndexとendIndexを含む)の各要素に inc を加算します。k回の操作をすべて実行した後の、変更後の配列を求めるのがこの問題です。 例えば、入力が length = 5、updates = [[1,3,2],[2,4,3],[0,2,-2]] の場合、出力は [-2, 0, 3, 5, 3]

  2. C++で点集合の線対称(ラインリフレクション)を判定するアルゴリズム

    問題概要2次元平面上にn個の点が与えられます。このとき、y軸に平行な直線で全ての点を鏡映(反射)した結果が、元の点集合と完全に一致するような直線が存在するかどうかを判定します。言い換えれば、ある直線を対称軸として全ての点を反転させたとき、反転後の点の集合が元の集合と同一になるかを確認する問題です。例えば、入力が points = [[1,1],[-1,1]] の場合を考えてみましょう。この場合、x = 0 の直線(y軸)を対称軸とすると、点 (1,1) は (-1,1) へ、(-1,1) は (1,1) へと移ります。点集合全体としては変化がないため、出力は true となります。解法のポイン