web-dev-qa-db-ja.com

数を1に減らすための最小ステップ数

任意の数nと、nに対する3つの操作を考えると:

  1. 1を追加
  2. 1を引く
  3. 数が偶数の場合、2で除算します

上記の操作の最小数を見つけて、nを1に減らしたいもっと早く。貪欲なアプローチ(偶数の場合は2で割り、奇数の場合は1を引く)でも最適な結果は得られません。別の解決策はありますか?

22
natydn52

一定時間内に最適な次のステップを知ることができるパターンがあります。実際、2つの等しく最適な選択肢がある場合があります。その場合、そのうちの1つを一定の時間で導出できます。

nのバイナリ表現とその最下位ビットを見ると、どの演算が解決につながるかについていくつかの結論を出すことができます。要するに:

  • 最下位ビットがゼロの場合、2で除算します
  • nが3の場合、または2つの最下位ビットが01の場合、減算
  • それ以外の場合:追加。

証明

最下位ビットがゼロの場合、次の操作は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コード

これにより、次のアルゴリズム(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 で実行されるのを確認してください。

JavaScriptスニペット

これは、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スクリプトを使用して、正確な結果を取得してください。

32
trincot

上記の問題を解決するには、再帰またはループのいずれかを使用します。再帰的な回答が既に提供されているため、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に到達するために必要なステップです

1
Prashant Negi

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
Ami Tavory

要約:

  • Nが偶数の場合、2で除算します
  • Nが3であるか、その最下位ビットが01である場合、減算します。
  • Nの最下位ビットが11の場合、追加します。

実行された操作の数をカウントして、1に達するまでnでこれらの操作を繰り返します。これにより、正しい答えが得られます。

@ trincotからの証明 の代替として、以下のケースがあり、願わくばより明確なものがあります:

証明:

ケース1:nは偶数

いくつかの操作を実行した後、yを数値の値にします。開始するには、y = n。

  1. Nを2で割るのが最適なアプローチではないと仮定します。
  2. 次に、偶数回加算または減算します
    1. 加算と減算を混在させると、不要な操作が発生するため、どちらか一方のみが実行されます。
    2. 奇数で停止すると、加算または減算が強制されるため、偶数を加算/減算する必要があります。
  3. 実行される加算または減算の数を2k(kは整数)とする
    1. N-2k> = 2になるように、減算時にkを制限します。
  4. 加算/減算後、y = n + 2k、またはy = n-2k。
  5. 今分割します。分割後、y = n/2 + k、またはy = n/2-k
  6. 2k + 1の操作が実行されました。ただし、最初に除算してからk回加算または減算することで、1 + kの演算で同じ結果を得ることができたはずです。
  7. したがって、分割が最適なアプローチではないという仮定は誤りであり、分割は最適なアプローチです。

ケース2:nは奇数です

ここでの目標は、奇数nに直面したときに、加算または減算のいずれかを実行すると、特定の状態に到達するための操作が少なくなることを示すことです。偶数に直面したときに分割が最適であるという事実を使用できます。

最下位ビットを示す部分的なビット列でnを表します。X1、X01など。Xは残りのビットを表し、ゼロではありません。 Xが0の場合、正解は明確です。1については完了です。 2(0b10)の場合、分割します。 3(0b11)の場合、減算して除算します。

試行1:1ビットの情報で加算または減算の方が良いかどうかを確認します。

  1. 開始:X1
    1. 追加:(X + 1)0、除算:X + 1(2操作)
    2. 減算:X0、除算:X(2演算)

行き止まりに到達します。XまたはX + 1が偶数の場合、最適な動きは分割することです。しかし、XとX + 1が偶数かどうかはわかりません。そのため、続行できません。

試行2:2ビットの情報で加算または減算の方が良いかどうかを確認します。

  1. 開始:X01
    1. 追加:X10、分割:X1
      1. 追加:(X + 1)0、除算:X + 1(4操作)
      2. 減算:X0、除算:X(4演算)
    2. 減算:X00、除算:X0、除算:X(3回の演算)
      1. 追加:X + 1(最適でない可能性があります)(4回の操​​作)

結論:X01の場合、減算の結果、少なくとも3つの演算と4つの演算と4つの演算が加算され、XとX + 1に到達します。

  1. 開始:X11
    1. 追加:(X + 1)00、除算:(X + 1)0、除算:X + 1(3操作)
      1. 減算:X(最適でない可能性があります)(4回の操​​作)
    2. 引き算:X10、除算:X1
      1. 追加:(X + 1)0、除算:X + 1(4操作)
      2. 減算:X0、除算:X(4演算)

結論:X11の場合、追加すると、少なくとも減算と同じ数の操作が発生します:X + 1およびXに到達するための3および4操作と4および4操作。

したがって、nの最下位ビットが01の場合、減算します。 nの最下位ビットが11の場合、追加します。

0
Jay Schauer

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))

申し訳ありませんが、より良いコメントをすることは知っていますが、私は始めたばかりです。

0
bjdduck