任意の数nと、nに対する3つの操作を考えると:
上記の操作の最小数を見つけて、nを1に減らしたいもっと早く。貪欲なアプローチ(偶数の場合は2で割り、奇数の場合は1を引く)でも最適な結果は得られません。別の解決策はありますか?
一定時間内に最適な次のステップを知ることができるパターンがあります。実際、2つの等しく最適な選択肢がある場合があります。その場合、そのうちの1つを一定の時間で導出できます。
nのバイナリ表現とその最下位ビットを見ると、どの演算が解決につながるかについていくつかの結論を出すことができます。要するに:
最下位ビットがゼロの場合、次の操作は2による除算になります。代わりに2つの加算と除算を試すこともできますが、同じ結果は除算と加算の2つのステップで実現できます。同様に2つの減算を使用します。そしてもちろん、無駄な後続の加算および減算ステップを無視できます(またはその逆)。そのため、最終ビットが0の場合、除算を行う方法です。
残りの3ビットパターンは**1
のようになります。それらの4つがあります。 a011
を記述して、ビット011
で終わる数値を示し、値aを表すプレフィックス付きビットのセットを使用します。
a001
:追加するとa010
が得られ、その後に分割が行われるはずです:a01
:2つのステップが実行されます。 1を減算したくないのは、それがa00
につながるためです。これは、最初から2ステップで到達する可能性があります(1を減算して除算します)。したがって、再び追加して除算してa1
を取得し、同じ理由でa+1
を指定して繰り返します。これには6つのステップが必要でしたが、5つのステップ(減算1、3回の除算、1の加算)で到達できる数になるため、明らかに加算を実行しないでください。減算は常に優れています。
a111
:加算は減算と同等またはそれより優れています。 4つのステップでa+1
を取得します。減算と除算はa11
になります。現在の追加は、最初の追加パスに比べて非効率的であるため、この減算/除算を2回繰り返し、6ステップでa
を取得します。 a
が0で終わる場合、a
が1で終わる場合は4でも、5ステップ(加算、3回除算、減算)でこれを行うことができます。常に良い。
a101
:減算と二重除算により、3ステップでa1
になります。加算と除算はa11
につながります。減算パスと比較すると、減算と除算は非効率的であるため、5ステップでa+1
を取得するために2回加算と除算を行います。しかし、減算パスを使用すると、4つのステップでこれに到達できます。したがって、減算は常に優れています。
a011
:加算と二重除算はa1
につながります。 a
を取得するにはa+1
を取得するためにさらに2つのステップ(5)が必要です:もう1つ(6)。減算、除算、減算、二重除算はa
(5)につながり、a+1
を取得するにはもう1ステップ(6)かかります。したがって、加算は少なくとも減算と同等です。ただし、見逃してはならないケースが1つあります。aが0の場合、減算パスは2ステップで途中で解に達しますが、加算パスは3ステップかかります。したがって、nが3の場合を除いて、加算は常にソリューションにつながります。減算を選択する必要があります。
したがって、奇数の場合、最後から2番目のビットが次のステップを決定します(3を除く)。
これにより、次のアルゴリズム(Python)が導かれます。このアルゴリズムでは、各ステップで1回の反復が必要であるため、O(logn)の複雑さが必要です。
def stepCount(n):
count = 0
while n > 1:
if n % 2 == 0: # bitmask: *0
n = n // 2
Elif n == 3 or n % 4 == 1: # bitmask: 01
n = n - 1
else: # bitmask: 11
n = n + 1
count += 1
return count
repl.it で実行されるのを確認してください。
これは、nの値を入力し、スニペットにステップ数を生成させるバージョンです。
function stepCount(n) {
var count = 0
while (n > 1) {
if (n % 2 == 0) // bitmask: *0
n = n / 2
else if (n == 3 || n % 4 == 1) // bitmask: 01
n = n - 1
else // bitmask: 11
n = n + 1
count += 1
}
return count
}
// I/O
var input = document.getElementById('input')
var output = document.getElementById('output')
var calc = document.getElementById('calc')
calc.onclick = function () {
var n = +input.value
if (n > 9007199254740991) { // 2^53-1
alert('Number too large for JavaScript')
} else {
var res = stepCount(n)
output.textContent = res
}
}
<input id="input" value="123549811245">
<button id="calc">Caluclate steps</button><br>
Result: <span id="output"></span>
JavaScriptの精度は約10に制限されていることに注意してください16、したがって、数値が大きくなると結果が間違ってしまいます。代わりにPythonスクリプトを使用して、正確な結果を取得してください。
上記の問題を解決するには、再帰またはループのいずれかを使用します。再帰的な回答が既に提供されているため、whileループアプローチを試みます。
論理:2の倍数は、2で割り切れないビットよりも常に少ないセットビットを持っていることに注意してください。
あなたの問題を解決するために、私はJavaコードを使用しています。コメントを追加したり、回答を編集しなければ、いくつかの数字で試してみましたが、うまくいきます。
while(n!=1)
{
steps++;
if(n%2 == 0)
{
n=n/2;
}
else
{
if(Integer.bitCount(n-1) > Integer.bitCount(n+1))
{
n += 1;
}
else
{
n -=1;
}
}
}
System.out.println(steps);
コードは非常に単純な形式で記述されているため、誰でも理解できます。ここでnは入力された番号であり、stepsは1に到達するために必要なステップです
n + 1またはn-1のどちらが貪欲に見える(奇数の場合)かどうかの見栄えがいいという考えが好きですが、もっと見た目を決めることを考えてください約束は、設定されたビットの総数を見るよりも少し良い方法で行うことができます。
番号x
の場合、
bin(x)[:: -1].index('1')
は、最初の1までの最下位0の数を示します。アイデアは、この数がn + 1またはn-1の場合に大きいかどうかを確認することです。 2つのうちの高い方を選択します(多くの連続する最下位0は、連続する半分が多いことを示します)。
これはにつながります
def min_steps_back(n):
count_to_1 = lambda x: bin(x)[:: -1].index('1')
if n in [0, 1]:
return 1 - n
if n % 2 == 0:
return 1 + min_steps_back(n / 2)
return 1 + (min_steps_back(n + 1) if count_to_1(n + 1) > count_to_1(n - 1) else min_steps_back(n - 1))
2つを比較するために、私は走った
num = 10000
ms, msb = 0., 0.
for i in range(1000):
n = random.randint(1, 99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999)
ms += min_steps(n)
msb += min_steps_back(n)
print ms / num, msb / num
どの出力
57.4797 56.5844
これは、平均して、使用する操作が少ないことを示しています(それほどではありませんが)。
要約:
実行された操作の数をカウントして、1に達するまでnでこれらの操作を繰り返します。これにより、正しい答えが得られます。
@ trincotからの証明 の代替として、以下のケースがあり、願わくばより明確なものがあります:
証明:
ケース1:nは偶数
いくつかの操作を実行した後、yを数値の値にします。開始するには、y = n。
ケース2:nは奇数です
ここでの目標は、奇数nに直面したときに、加算または減算のいずれかを実行すると、特定の状態に到達するための操作が少なくなることを示すことです。偶数に直面したときに分割が最適であるという事実を使用できます。
最下位ビットを示す部分的なビット列でnを表します。X1、X01など。Xは残りのビットを表し、ゼロではありません。 Xが0の場合、正解は明確です。1については完了です。 2(0b10)の場合、分割します。 3(0b11)の場合、減算して除算します。
試行1:1ビットの情報で加算または減算の方が良いかどうかを確認します。
行き止まりに到達します。XまたはX + 1が偶数の場合、最適な動きは分割することです。しかし、XとX + 1が偶数かどうかはわかりません。そのため、続行できません。
試行2:2ビットの情報で加算または減算の方が良いかどうかを確認します。
結論:X01の場合、減算の結果、少なくとも3つの演算と4つの演算と4つの演算が加算され、XとX + 1に到達します。
結論:X11の場合、追加すると、少なくとも減算と同じ数の操作が発生します:X + 1およびXに到達するための3および4操作と4および4操作。
したがって、nの最下位ビットが01の場合、減算します。 nの最下位ビットが11の場合、追加します。
AMI Tavoyが提供するソリューションは、3を考慮した場合に機能します(4に追加すると、0b100
およびcount_to_1
は2になります。これは、0b10
およびcount_to_1
の2 1)。 n = 3になったときに2つのステップを追加して、ソリューションを終了できます。
def min_steps_back(n):
count_to_1 = lambda x: bin(x)[:: -1].index('1')
if n in [0, 1]:
return 1 - n
if n == 3:
return 2
if n % 2 == 0:
return 1 + min_steps_back(n / 2)
return 1 + (min_steps_back(n + 1) if count_to_1(n + 1) > count_to_1(n - 1) else min_steps_back(n - 1))
申し訳ありませんが、より良いコメントをすることは知っていますが、私は始めたばかりです。