Greatest Common Divisor
2 つの正の整数 a と b が与えられます。それらの最大公約数、つまり両方を余りなく割り切れる最大の整数を返してください。
たとえば、8 と 12 の両方を割り切れる数は 1、2、4 なので、答えは 4 です。
関数
- ainteger
- 最初の正の整数
- binteger
- 2番目の正の整数
- 戻り値integer
- a と b の両方を割り切る最大の整数
制約
1 ≤ a ≤ 1091 ≤ b ≤ 109
例
- 入力
- a = 12b = 18
- 出力
- 6
- 説明
12の約数は1、2、3、4、6、12です。18の約数は1、2、3、6、9、18です。両方のリストにある最大の数は6です。
- 入力
- a = 17b = 5
- 出力
- 1
- 説明
17と5はどちらも素数で、異なるため、共通する唯一の約数は1です。
- 入力
- a = 42b = 42
- 出力
- 42
- 説明
- 数はそれ自身で割り切れ、
42より大きい数で42を割り切れるものはないため、42と42の最大公約数は42です。
提出時に隠しテスト+14件
発展問題
ユークリッドの互除法を拡張して、a × x + b × y = gcd(a, b)を満たす整数xとyも返せるようにできますか?
ヒント
1つずつ開いてください。開くたびに少しずつ答えに近づきます。
aとbの公約数が、2つの数のうち小さい方より大きくなることはありません。10^9に近い2つの数の場合、候補をいくつ試す必要があるでしょうか?aとbの両方を割り切る数は、a % bも割り切ります。したがって、gcd(a, b)はgcd(b, a % b)に等しく、後者の組の方が小さくなります。組
(a, b)を(b, a % b)に置き換え続けます。2番目の数が0になったら、最初の数が答えです。
解説
定義からは候補を1つずつ試すことが考えられます。小さな数ならそれでうまくいきます。しかし、a と b が 10^9 までの範囲にある場合、共通の因数を持たない2つの大きな数では、10億回の試行が必要になります。ユークリッドの、gcd(a, b) は gcd(b, a % b) に等しいという観察により、数は非常に速く小さくなるため、10^9 までのどのような数のペアでも、43ステップを超えることはありません。
小さい数からカウントダウンする
正しいが、最大のテストでは終わらない
考え方
共通の約数は、2つの数のうち小さい方より大きくなることはありません。なぜなら、bの約数は最大でもbだからです。そこで、候補となるdをmin(a, b)から始め、両方を割り切るまで1ずつ減らします。候補を大きい方から試すため、条件を満たす最初のものが最大公約数です。
12と18の場合、12(18を割り切らない)、次に11、10、9、8、7を試しますが、どれも条件を満たさず、6で終了します。1はすべての数を割り切るため、ループは必ず終了します。
計算量は候補の数に比例します。2つの素数である999999937と999999929の場合、答えは1で、ループはほぼ10^9回実行されます。最大規模のテストには遅すぎます。
アルゴリズム
aとbのうち小さい方をdに設定します。a % dまたはb % dが0でない間、dから 1 を引きます。dを返します。
def gcd(a, b):
d = min(a, b)
while a % d != 0 or b % d != 0:
d -= 1
return dユークリッドの互除法
考え方
r = a % b となるように a = q × b + r と書きます。a と b の両方を割り切る数は、r = a - q × b も割り切ります。b と r の両方を割り切る数は、a = q × b + r も割り切ります。したがって、組 (a, b) と (b, r) は、まったく同じ公約数を持ち、最大公約数も同じです。
(a, b) を (b, a % b) に置き換え、b が 0 になるまで繰り返します。すべての数は 0 を割り切るので、gcd(a, 0) = a となり、a が答えです。12 と 18 の場合、(12, 18) は (18, 12)、次に (12, 6)、さらに (6, 0) となり、答えは 6 です。最初のステップで、小さい方が a の場合は自動的に数値が入れ替わるため、並べ替える必要はありません。
2ステップごとに大きい方の数は少なくとも半分になるため、ループの実行回数は O(log(min(a, b))) です。最も時間のかかる入力は、701408733 と 433494437 のような連続するフィボナッチ数ですが、それでもわずか42ステップです。
アルゴリズム
bが0でない間、r = a % bを計算します。a = b、b = rを設定します。bが0になったら、aを返します。
def gcd(a, b):
# gcd(a, b) == gcd(b, a % b), and gcd(a, 0) == a.
while b != 0:
a, b = b, a % b
return a
落とし穴と境界ケース
アルゴリズムは短いため、バグは更新処理と終了条件から生じます。
- 更新する順序を間違える。
a = bの後にb = a % bを実行すると、b % bが計算されます。これは常に0となり、bを返します。余りを先に一時変数に保存するか、両方を一度に代入してください。 - ループ終了時に
aではなくbを返す。その時点でbは0です。 - カウントダウンを
2で止める、またはmax(a, b)から開始する。前者では17と5のような互いに素な組を見落とし、後者では小さい方の数を割れない候補に時間を無駄にします。 - 余りの代わりに繰り返し引き算を使う。すると
gcd(10^9, 1)には10億回の引き算が必要になります。%ならそれらをすべて1回で行えます。
よくある質問4
ユークリッドのアルゴリズムの時間計算量はどのくらいですか?
O(log(min(a, b))) ステップで実行されます。2ステップごとに、大きい方の数が少なくとも半分になるためです。最悪のケースは、連続するフィボナッチ数のペアです。10^9 以下の数の場合、ステップ数は最大でも43で、アルゴリズムが使用する追加の領域は O(1) です。
なぜ gcd(a, b) は gcd(b, a % b) と等しいのでしょうか?
a = q × b + r(r = a % b)と書きます。a と b を割り切る数は、a - q × b、つまり r も割り切ります。b と r を割り切る数は、q × b + r、つまり a も割り切ります。どちらの組も共通の約数が同じなので、最大公約数も同じです。
GCDとLCMの違いは何ですか?
最大公約数は両方の入力を割り切る最大の数で、最小公倍数は両方の入力で割り切れる最小の数です。これらは gcd(a, b) × lcm(a, b) = a × b で結び付いているため、最大公約数が分かれば、最小公倍数は a / gcd(a, b) × b です。
互いに素な2つの数の最大公約数は何ですか?
2つの数の最大公約数が1のとき、それらは互いに素です。つまり、共通の素因数を持ちません。異なる2つの素数は常に互いに素であり、8と9のような連続する整数も互いに素です。
似た問題
同じ考え方を使う問題です。2〜3問解くとパターンが身につきます。
Python
def gcd(a, b):
# ここにコードを書いてくださいケース1
ケース2
ケース3
入力
a = 12 b = 18
期待値
6