Минимальное количество шагов для уменьшения числа до 1
Учитывая любое число n и три операции над n:
- добавить 1
- вычесть 1
- разделите на 2, если число четное
Я хочу найти минимальное количество вышеперечисленных операций, чтобы уменьшить n до 1. Я пробовал динамический подход для прогамминга, также BFS с обрезкой, но n может быть очень большим (10 ^ 300), и я не знаю, как сделайте мой алгоритм быстрее. Жадный подход (делить на 2, если даже и вычесть 1, если нечетно) также не дает оптимального результата. Существует ли другое решение?
Ответы
Ответ 1
Существует шаблон, который позволяет вам знать оптимальный следующий шаг в постоянное время. На самом деле, могут быть случаи, когда есть два одинаково оптимальных варианта - в этом случае один из них может быть получен за постоянное время.
Если вы посмотрите на двоичное представление n и его наименее значимые биты, вы можете сделать некоторые выводы о том, какая операция приводит к решению. Короче говоря:
- если младший бит равен нулю, то разделите на 2
- если n равно 3 или 2 младших значащих бита равны 01, то вычтите
- Во всех остальных случаях: доп.
Доказательство
Если младший значащий бит равен нулю, следующей операцией должно быть деление на 2. Вместо этого мы можем попробовать 2 сложения и затем деление, но тогда тот же самый результат может быть достигнут в два этапа: разделить и сложить. Аналогично с 2 вычитаниями. И, конечно же, мы можем игнорировать бесполезное последующее добавление & вычитать шаги (или наоборот). Поэтому, если последний бит равен 0, деление - это путь.
Тогда остальные 3-битные комбинации будут похожи на **1
. Их четыре. Позвольте написать a011
, чтобы обозначить число, которое заканчивается битами 011
и имеет набор префиксных битов, которые будут представлять значение a:
a001
: добавление одного даст a010
, после чего должно произойти деление: a01
: сделано 2 шага. Мы бы не хотели вычитать один сейчас, потому что это привело бы к a00
, к которому мы могли бы прийти в двух шагах от начала (вычесть 1 и разделить). Итак, снова мы добавляем и делим, чтобы получить a1
, и по той же причине мы повторяем это снова, давая: a+1
. Это заняло 6 шагов, но приводит к числу, которое можно получить за 5 шагов (вычтите 1, разделите 3 раза, добавьте 1), поэтому ясно, что мы не должны выполнять сложение. Вычитание всегда лучше.
a111
: сложение равно или лучше, чем вычитание. В 4 шага мы получаем a+1
. Вычитание и деление дадут a11
. Добавление сейчас будет неэффективным по сравнению с исходным путем сложения, поэтому мы повторим это вычитание/деление дважды и получим a
за 6 шагов. Если a
заканчивается на 0, то мы могли бы сделать это за 5 шагов (сложить, разделить три раза, вычесть), если a
заканчивается на 1, то даже на 4. Так что сложение всегда лучше.
a101
: вычитание и двойное деление приводят к a1
в 3 этапа. Добавление и разделение приводит к a11
. Теперь вычитать и делить было бы неэффективно по сравнению с путем вычитания, поэтому мы добавляем и делим дважды, чтобы получить a+1
за 5 шагов. Но с помощью пути вычитания мы могли бы достичь этого за 4 шага. Так что вычитание всегда лучше.
a011
: сложение и двойное деление приводит к a1
. Чтобы получить a
потребуется еще 2 шага (5), чтобы получить a+1
: еще один (6). Вычитание, деление, вычитание, двойное деление приводит к a
(5), для получения a+1
потребуется еще один шаг (6). Таким образом, сложение по крайней мере так же хорошо, как вычитание. Однако есть один случай, который нельзя упускать: если a равно 0, то путь вычитания достигает решения на полпути, за 2 шага, а путь сложения - за 3 шага. Таким образом, сложение всегда приводит к решению, за исключением случаев, когда n равно 3: тогда следует выбрать вычитание.
Таким образом, для нечетных чисел второй-последний бит определяет следующий шаг (кроме 3).
Код Python
Это приводит к следующему алгоритму (Python), который требует одну итерацию для каждого шага и, следовательно, должен иметь сложность 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>
Ответ 2
Мне нравится идея, вызванная жадным взглядом (для случая нечетных чисел), является ли n + 1 или n - 1 более перспективным, но думаю, что решение о том, что выглядит более перспективным, можно сделать немного лучше, чем смотреть на общее количество установленных бит.
Для числа x
,
bin(x)[:: -1].index('1')
указывает количество наименьших значащих 0s до первого 1. Идея состоит в том, чтобы определить, является ли это число выше для n + 1 или n - 1, и выберите более высокий из двух (много последовательных наименее- Значительные 0s указывают на большее количество сокращений в два раза).
Это приводит к
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))
Чтобы сравнить эти два, я побежал
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
показывает, что в среднем это делает меньше операций (хотя и не так).
Ответ 3
Чтобы решить эту проблему, вы можете использовать рекурсию или циклы
Рекурсивный ответ уже предоставлен, поэтому я постараюсь дать подход к циклу 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 введено число и шаги - шаги, необходимые для достижения 1
Ответ 4
Решение, предлагаемое Ами Тавой, работает, если рассматривать 3 (добавление к 4 будет производить 0b100
и count_to_1
равно 2, что больше, чем вычитание в 2 для 0b10
и count_to_1
равно 1). Вы можете добавить два шага, когда мы опустим n = 3, чтобы завершить решение:
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))
Извините, я знаю, сделаю лучший комментарий, но я только начал.
Ответ 5
В итоге:
- Если n четное, разделите на 2
- Если n равно 3 или его младшие значащие биты равны 01, вычтите.
- Если n младших битов равно 11, добавьте.
Повторите эти операции для n, пока не достигнете 1, считая количество выполненных операций. Это гарантированно даст правильный ответ.
В качестве альтернативы доказательству из @trincot, здесь приведено меньше случаев, и, как мы надеемся, более ясное:
Доказательство:
Случай 1: n чётно
Позвольте y быть значением числа после выполнения некоторых операций над ним. Для начала у = п.
- Предположим, что деление n на 2 не является оптимальным подходом.
- Затем либо добавить или вычесть четное количество раз
- Смешивание сложения и вычитания приведет к ненужным операциям, поэтому выполняется только одна из них.
- Необходимо добавить/вычесть четное число, так как остановка на нечетном числе приведет к продолжению сложения или вычитания.
- Пусть 2k, где k - некоторое целое число, будет количеством выполненных сложений или вычитаний
- Ограничьте k при вычитании так, чтобы n - 2k> = 2.
- После сложения/вычитания y = n + 2k или y = n - 2k.
- Теперь разделите. После деления y = n/2 + k или y = n/2 - k
- Выполнено 2к + 1 операций. Но того же результата можно было бы достичь в 1 + k операциях, сначала разделив, а затем сложив или вычтя k раз.
- Таким образом, предположение, что деление не является оптимальным подходом, было неверным, а деление - оптимальным подходом.
Случай 2: n нечетно
Цель здесь - показать, что при столкновении с нечетным n добавление или вычитание приведет к меньшему количеству операций для достижения заданного состояния. Мы можем использовать тот факт, что деление является оптимальным, когда сталкиваемся с четным числом.
Мы представим n с частичной цепочкой битов, показывающей младшие значащие биты: X1, или X01, и т.д., Где X представляет оставшиеся биты и отличен от нуля. Когда X равен 0, правильные ответы ясны: для 1 все готово; для 2 (0b10) разделить; для 3 (0b11) вычтите и разделите.
Попытка 1: Проверьте, лучше ли сложение или вычитание, с одним битом информации:
- Начало: X1
- добавить: (X + 1) 0, разделить: X + 1 (2 операции)
- вычесть: X0, разделить: X (2 операции)
Мы зашли в тупик: если бы Х или Х + 1 были четными, оптимальным было бы деление. Но мы не знаем, четны ли X или X + 1, поэтому мы не можем продолжать.
Попытка 2: Проверьте, лучше ли сложить или вычесть, с помощью двух битов информации:
- Начало: X01
- добавить: X10, разделить: X1
- добавить: (X + 1) 0, разделить: X + 1 (4 операции)
- вычесть: X0, разделить: X (4 операции)
- вычесть: X00, разделить: X0, разделить: X (3 операции)
- добавить: X + 1 (возможно, не оптимально) (4 операции)
Вывод: для X01 вычитание приведет к как минимум такому количеству операций, как сложение: 3 и 4 операции против 4 и 4 операций для достижения X и X + 1.
- Начало: X11
- добавить: (X + 1) 00, разделить: (X + 1) 0, разделить: X + 1 (3 операции)
- вычесть: X (возможно, не оптимально) (4 операции)
- вычесть: X10, разделить: X1
- добавить: (X + 1) 0, разделить: X + 1 (4 операции)
- вычесть: X0, разделить: X (4 операции)
Вывод: для X11 добавление приведет к вычитанию как минимум такого количества операций, как вычитание: 3 и 4 операции против 4 и 4 операций для достижения X + 1 и X.
Таким образом, если n младших значащих битов равны 01, вычтите. Если n младших битов равны 11, добавьте.
Ответ 6
Я очень плохо разбираюсь в двоичных файлах, поэтому не считая lsb или msb.
Как насчет программы ниже -
public class ReduceNto1 {
public static void main(String[] args) {
int count1 = count(59);//input number
System.out.println("total min steps - " + count1);
}
static int count(int n){
System.out.println(n + " > ");
if(n==1){
return 0;
}
else if(n %2 ==0){
return 1 + count(n/2);
}else{
return 1 + Math.min(count(n-1), count(n+1));
}
}
}