minimum number of steps to reduce number to 1
Asked Answered
S

11

58

Given any number n, and three operations on n:

  1. add 1
  2. subtract 1
  3. divide by 2 if the number is even

I want to find the minimum number of the above operations to reduce n to 1. I have tried dynamic progamming approach, also BFS with pruning, but n can be very large (10^300) and I do not know how to make my algorithm faster. The greedy approach (divide by 2 if even and subtract 1 if odd) also does not give the optimal result. Is another solution exists?

Single answered 20/9, 2016 at 7:52 Comment(10)
The greedy approach ... does not give the optimal result ... can you give a number for which this is not optimal?Oleg
15, greedy will give 6 (14 -> 7 -> 6 -> 3 -> 2 -> 1) but optimal is 5 (16 -> 8 -> 4 -> 2 -> 1)Single
Sounds like you want to approach a power of 2 if possible.Derisive
Do a variant of the greedy approach, but at each step check whether it's quicker to get to the nearest power of 2 and divide until 1.Forestation
Problem statement needs to be more clear. You want min number of above operations, but can i use other operations (e.g. multiply, add two numbers) to calculate the min number of steps?Irv
Not sure if this is optimal but it seems whenever there are 4 or more ones in the binary representation at the end do an add step, otherwise do subtract or divide.Losse
@trincot You don't understand my question. For example, The answer proposed by Aylon will calculate the number of operations needed. But to figure that out, the solution uses array indexing (which requires multiplication and addition of two numbers), as well as min operation, which requires subtraction of two numbers.Irv
@JoelLee, indeed I misunderstood your earlier comment. Thanks for having clarified.Inflatable
See also the 3n+1 problem: n even -> n / 2, n odd -> 3n+1. Afaik it's still unsolved.Intransigeance
For reference, this is a problem from the foobar.withgoogle.com game. Even the 10^300 problem size is taken straight from there. Curiously, the proposed solution (to which I also arrived independently) does not pass all test cases.Transmitter
I
88

There is a pattern which allows you to know the optimal next step in constant time. In fact, there can be cases where there are two equally optimal choices -- in that case one of them can be derived in constant time.

If you look at the binary representation of n, and its least significant bits, you can make some conclusions about which operation is leading to the solution. In short:

  • if the least significant bit is zero, then divide by 2
  • if n is 3, or the 2 least significant bits are 01, then subtract
  • In all other cases: add.

Proof

If the least significant bit is zero, the next operation should be the division by 2. We could instead try 2 additions and then a division, but then that same result can be achieved in two steps: divide and add. Similarly with 2 subtractions. And of course, we can ignore the useless subsequent add & subtract steps (or vice versa). So if the final bit is 0, division is the way to go.

Then the remaining 3-bit patterns are like **1. There are four of them. Let's write a011 to denote a number that ends with bits 011 and has a set of prefixed bits that would represent the value a:

  • a001: adding one would give a010, after which a division should occur: a01: 2 steps taken. We would not want to subtract one now, because that would lead to a00, which we could have arrived at in two steps from the start (subtract 1 and divide). So again we add and divide to get a1, and for the same reason we repeat that again, giving: a+1. This took 6 steps, but leads to a number that could be arrived at in 5 steps (subtract 1, divide 3 times, add 1), so clearly, we should not perform the addition. Subtraction is always better.

  • a111: addition is equal or better than subtraction. In 4 steps we get a+1. Subtraction and division would give a11. Adding now would be inefficient compared to the initial addition path, so we repeat this subtract/divide twice and get a in 6 steps. If a ends in 0, then we could have done this in 5 steps (add, divide three times, subtract), if a ends in a 1, then even in 4. So Addition is always better.

  • a101: subtraction and double division leads to a1 in 3 steps. Addition and division leads to a11. To now subtract and divide would be inefficient, compared to the subtraction path, so we add and divide twice to get a+1 in 5 steps. But with the subtraction path, we could reach this in 4 steps. So subtraction is always better.

  • a011: addition and double division leads to a1. To get a would take 2 more steps (5), to get a+1: one more (6). Subtraction, division, subtraction, double division leads to a (5), to get a+1 would take one more step (6). So addition is at least as good as subtraction. There is however one case not to overlook: if a is 0, then the subtraction path reaches the solution half-way, in 2 steps, while the addition path takes 3 steps. So addition is always leading to the solution, except when n is 3: then subtraction should be chosen.

So for odd numbers the second-last bit determines the next step (except for 3).

Python Code

This leads to the following algorithm (Python), which needs one iteration for each step, making for O(log𝑛) iterations. If we consider that basic arithmetic operations on large integers have a log𝑛 complexity, this brings us to a time complexity of O(log²𝑛):

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

See it run on repl.it.

JavaScript Snippet

Here is a version where you can input a value for n and let the snippet produce the number of steps:

function stepCount(n) {
    var count = 0n;
    while (n > 1n) {
        if (n % 2n == 0) {                    // bitmask: *0
            n /= 2n;
        } else if (n == 3n || n % 4n == 1n) { // bitmask: 01
            n--;
        } else {                              // bitmask: 11
            n++;
        }
        count++;
    }
    return count;
}

// I/O
var input = document.getElementById('input');
var output = document.getElementById('output');
var calc = document.getElementById('calc');

calc.onclick = function () {
  var n = BigInt(input.value);
  var res = stepCount(n);
  output.textContent = res;
}
<input id="input" value="123549811245">
<button id="calc">Calculate steps</button><br>
Result: <span id="output"></span>

This script uses BigInt typed values to allow for arbitrary large integers as is the case in Python.

Inflatable answered 20/9, 2016 at 8:44 Comment(16)
This seems to need a really huge cache. the number can be as big as 10^300Kingsly
I rewrote my answer completely. I believe it is now the fastest solution posted. It needs no cache, no recursion.Inflatable
Nice algorithm, avoids unnecessary "tail recursion". Minor edit suggestion: Remove "Obviously," from your proof. Makes intuitive sense, but not obvious and, in fact, requires proof (which you did).Irv
First, @Inflatable this is an excellent answer, so thank you! I was wondering if you could talk about what led you limit your scope to only three bits?Christine
I don't remember how I came to that... I suppose it is one of those peculiar things of the human brain: you try a few things (like using the number of 1 bits), have some subconscious intuitions going on, and after several failing ideas there is the successful one bubbling up which turns out to hold up when analysing all possible scenarios. I might have looked at the 4 ending bits before the final answer, realizing the proof could be given with only 3, although the second bullet point actually still talks about the 4th bit.Inflatable
Also, I am not really grasping why you are trying to get to a+1 and why you are avoiding a00 in the first bullet point. Also maybe you can point me in the direction of some resources to help me understand how (a1 + 1) / 10 = a+1. Does a+1 represent some sort of signed binary number, or maybe a remainder?Christine
@Will, the cases where the last bit is 0 (like a00) are dealt with in the first paragraph of the proof. For a+1. a could be anything, but let's assume it is 101. So a1 is 1011. Adding one to a1 is then 1011+1 = 1100. Dividing by 2 gives 110, which indeed is 101+1 (a+1). a is the number you get when chopping off the 3 rightmost bits after the original number.Inflatable
I made a breadth first search to confirm this answer and it works out for the first 1400 integers. Makes sense upon inspection. With the 2^x line in kind should we choose 1(first bit), 2(first two bits), 4 (three bits) or 8(four) or higher powers of 2 as a filter? Choosing 1 or two wouldn’t filter anything. 4 (3 sig bits) is the first that filters anything and any higher power is only redundant. Great answer.Ulda
@Inflatable I am working on a similar problem, where I have to reach a number n from 1 in minimum steps. My list of allowed operations are slightly more complex. Apart from +1, -1 I have where *2, /2 and the last operation where I can sort the digits of the number in descending order. I tried BFS approach but got TLE (obviously), Then I came across this solution of yours. Can you help me?? Problem link : hackerrank.com/challenges/food-delivery-service/problemKannry
It is better to post your attempt as a new question.Inflatable
@Inflatable In the case of a011, how do you move from a1 to a+1 in 1 step? a1+1=(a+1)0, and a1-1=a0Extraneous
Actually I wrote up an alternative proof, can you take a look @Inflatable https://mcmap.net/q/329980/-minimum-number-of-steps-to-reduce-number-to-1Extraneous
@JaySchauer, The move is not from a1 to a+1 in one step. I had a typo in the number between parentheses (the accumulated number of steps), which gave the impression the step was from a1, while it really is from a. So its "(6)", not "(4)". Thanks for having spotted it ;-)Inflatable
This answer is O(log(n)) time complexity only when the input fits integer. With BigNum (long) the complexety multiplies by log(n) / (length of integer in bits).Aery
@StanislavGerman-Evtushenko, I updated my answer to include this aspect. But note that the length of a Big Integer in bits is O(log𝑛), so "log(n) / (length of integer in bits)" is O(1) for Big Integer types (that have a dynamic size). But it is true we have to factor in O(log𝑛) for arithmetic operations on big integers with 𝑛 bits.Inflatable
"length of integer in bits", not "length of big integer", either way its O(log^2(n)) as in your updated answerAery
E
7

In summary:

  • If n is even, divide by 2
  • If n is 3 or its least significant bits are 01, subtract.
  • If n's least significant bits are 11, add.

Repeat these operations on n until you reach 1, counting the number of operations performed. This is guaranteed to give the correct answer.

As an alternative to the proof from @trincot, here is one that has less cases and is hopefully more clear:

Proof:

Case 1: n is even

Let y be the value of the number after performing some operations on it. To start, y = n.

  1. Assume that dividing n by 2 is not the optimal approach.
  2. Then either add or subtract an even number of times
    1. Mixing addition and subtraction will result in unnecessary operations, so only either one is done.
    2. An even number must be added/subtracted, since stopping on an odd number would force continued adding or subtracting.
  3. Let 2k, where k is some integer, be the number of additions or subtractions performed
    1. Limit k when subtracting so that n - 2k >= 2.
  4. After adding/subtracting, y = n + 2k, or y = n - 2k.
  5. Now divide. After dividing, y = n/2 + k, or y = n/2 - k
  6. 2k + 1 operations have been performed. But the same result could have have been achieved in 1 + k operations, by dividing first then adding or subtracting k times.
  7. Thus the assumption that dividing is not the optimal approach was wrong, and dividing is the optimal approach.

Case 2: n is odd

The goal here is to show that when faced with an odd n, either adding or subtracting will result in less operations to reach a given state. We can use that fact that dividing is optimal when faced with an even number.

We will represent n with a partial bitstring showing the least significant bits: X1, or X01, etc, where X represents the remaining bits, and is nonzero. When X is 0, the correct answers are clear: for 1, you're done; for 2 (0b10), divide; for 3 (0b11), subtract and divide.

Attempt 1: Check whether adding or subtracting is better with one bit of information:

  1. Start: X1
    1. add: (X+1)0, divide: X+1 (2 operations)
    2. subtract: X0, divide: X (2 operations)

We reach an impass: if X or X+1 were even, the optimal move would be to divide. But we don't know if X or X+1 are even, so we can't continue.

Attempt 2: Check whether adding or subtracting is better with two bits of information:

  1. Start: X01
    1. add: X10, divide: X1
      1. add: (X+1)0, divide: X+1 (4 operations)
      2. subtract: X0, divide: X (4 operations)
    2. subtract: X00, divide: X0, divide: X (3 operations)
      1. add: X+1 (possibly not optimal) (4 operations)

Conclusion: for X01, subtracting will result in at least as few operations as adding: 3 and 4 operations versus 4 and 4 operations to reach X and X+1.

  1. Start: X11
    1. add: (X+1)00, divide: (X+1)0, divide: X+1 (3 operations)
      1. subtract: X (possibly not optimal) (4 operations)
    2. subtract: X10, divide: X1
      1. add: (X+1)0, divide: X+1 (4 operations)
      2. subtract: X0, divide: X (4 operations)

Conclusion: for X11, adding will result in at least as few operations as subtracting: 3 and 4 operations versus 4 and 4 operations to reach X+1 and X.

Thus, if n's least significant bits are 01, subtract. If n's least significant bits are 11, add.

Extraneous answered 7/8, 2019 at 4:16 Comment(0)
H
3

If you consider the binary representation of any positive integer and the operations allowed you will come up with the following:

  1. Any sequence of 1s will be dealt with by adding 1

  2. Any 1 which is not part of a sequence will be dealt with by subtracting 1

  3. The total number of divisions required will be the either the number of binary digits or the number of binary digits minus 1 depending on whether the last operation was an addition of 1 resulting in an extra bit on the number ( e.g. 1111 would become 10000 requiring an extra division while 1000 would require a total of 3 divisions )

  4. There is a special case for the number 3 (11) where subtracting one is faster than adding one requiring 2 steps , a subtraction and a division instead of 3 steps, an addition and 2 divisions.

Point being you don't really need to do any operations to count the steps. All you need to do is loop once through the bits of the number and identify how many of the above you encounter. Though every time an addition of one is to occur the bit left to the sequence of 1s will need to be switched to 1.

Here's a sloppy python implementation of the above concept:

def countSteps(n):
    count = 0
    k = bin(n)[2:]
    i = len(k)-1
    le = len(k)
    k = list(k)
    subs = 0
    adds = 0
    divs = 0

    if n == 1:
        return 0

    while i>=0:
        ones=0
        while k[i] == '1' and i >=0:
            ones+=1
            i-=1

        if ones == 1 and i > 0:
            subs+=1
        if ones >1:
            #special case for 3
            if i < 0 and ones == 2:
                subs+=1
                divs-=1
            else:
                adds+=1
                k[i]='1'
                i+=1
        i-=1

    if k[1] == '1':
        divs = divs+le
    else:
        divs = divs+le-1

    return divs + subs + adds

This approach is likely to be very fast. Significantly faster than any approach requiring modulo to determine the next step.

Hipparchus answered 31/10, 2021 at 19:10 Comment(0)
T
2

To solve the above problem you can either use recursion or loops A recursive answer is already provided so i would try to give a while loop approach.

Logic: We should remember that the number multiple of 2 would always have less set bits than those which are not divisible by 2.

To solve your problem i'm using java code. I have tried it with few numbers and it works fine, if it doesn't add a comment or edit the answer

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

The code is written in a very simple form so that it could be understood by everyone. Here n is the number entered and steps are the steps required to reach 1

Tonry answered 20/9, 2016 at 11:34 Comment(1)
This function gives the wrong result for 59. It returns 9 steps, while the correct answer is 8. The first step it does for 59 is -1, while it should choose +1. The counting of set bits is thus not a sound basis to find the shortest path. Also: the statement in the "logic" paragraph is not correct: 5 (odd) has 2 set bits, while 14 (even) has 3. The statement needs to be further qualified to make it correct.Inflatable
H
1

I like the idea by squeamish ossifrage of greedily looking (for the case of odd numbers) whether n + 1 or n - 1 looks more promising, but think deciding what looks more promising can be done a bit better than looking at the total number of set bits.

For a number x,

bin(x)[:: -1].index('1')

indicates the number of least-significant 0s until the first 1. The idea, then, is to see whether this number is higher for n + 1 or n - 1, and choose the higher of the two (many consecutive least-significant 0s indicate more consecutive halving).

This leads to

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

To compare the two, I ran

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

Which outputs

57.4797 56.5844

showing that, on average, this does use fewer operations (albeit not by that much).

Hawkie answered 20/9, 2016 at 10:32 Comment(4)
Should be if n in [0, 1]: return 1-n, but otherwise this looks good :-) +1Specht
@squeamishossifrage Thanks! Once again, really liked your answer (can't upvote it more than once).Hawkie
The division should be an integer division (// instead of /). Also: this function gives the wrong result for 3, 6, 11, 12, 13, and many others: in all these cases it returns 1 step more than the optimal solution.Inflatable
@Inflatable Thanks, I'll check it. In any case, my answer is just a heuristic, not an optimal solution.Hawkie
S
1

I am really bad at binaries so not counting the lsb or msb. What about below program -

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));
        }
    }
}
Seism answered 3/10, 2019 at 12:51 Comment(2)
it return 8 for 59. It returns 5 for 15Seism
I think you would not be able to solve it for large numbersPyrochemical
N
1

Although everyone has already answered the question with in depth analysis, I want to share one intuition for the readers. (Note: There is no formal proof in my answer)

  • We can agree that it is better to divide by 2 when number is even.
  • Now for the odd case consider last 2 LSBs of n.
  • Case 1: 01 -> If we subtract 1 they will become 00 allowing us to divide 2 times in the subsequent steps. (As opposed to adding 1 which will make them 10)
  • Case 2: 11 -> If we add 1 they will become 00 allowing us to divide 2 times in the subsequent steps. (As opposed to subtracting 1 which will make them 10). Special case is 3 as already discussed in other answers.
Nealy answered 28/7, 2021 at 8:37 Comment(0)
U
0

The solution offered by Ami Tavoy works if the 3 is considered (adding to 4 would produce 0b100 and count_to_1 equals 2 which is greater than subtracting to 2 for 0b10 and count_to_1 equals 1). You can add two steps when we get down no n = 3 to finish off the solution:

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

Sorry, I know would make a better comment, but I just started.

Upsilon answered 3/5, 2017 at 22:34 Comment(1)
Welcome to SO! This looks like a comment to the question instead of an answer. If you intend to comment, you need to have sufficient reputation to comment on any post. Also check this what can I do instead.Aryl
G
0

As @trincot pointed out, we should always try to divide by two the number so, but one easy way to see why if the number is odd we should decrease by 1 if it's 3 or ends it "01", and add 1 in the other case is this. If n is odd, n % 4 will be 1 or 3, then n+1 or n-1 are going to be multiples of 4, which means that we are going to be able to divide twice the number by two.

Galloway answered 21/12, 2020 at 18:19 Comment(2)
This should be a comment.Luke
@CarySwoveland they don't have the reputation required to commentScever
B
0

Based on @trincot answer, an alternative way to verify the 2 LSB's is simply using bin(n)[-2:] and voila for those who don't want to deal with binaries!

Bock answered 23/1, 2021 at 5:48 Comment(2)
This should be a comment.Luke
@CarySwoveland they don't have the reputation required to commentScever
K
0

This is my solution in Java with a time complexity of O(1):

int solution(int n) {
    int count = 0;
    int plus1Operations = ((n >> 1) & n) | ((n >> 1) & ~n); //(Over)estimate how many "+1" operations are required
    plus1Operations &= ~(n + plus1Operations);              //Adjust the estimate

    if(plus1Operations > (n >> 2))  //If by mistake we used the "+1" operation with 3
        count = -1;                 //Start counting from -1

    //Do all operations "+1"; each remaining bit set to 1 in 'n' means a "-1" operation, except for the most-significant bit set to 1
    n += plus1Operations;

    count += Integer.bitCount(n | plus1Operations);               //Count all "+1" and "-1" operations
    count += Integer.SIZE - Integer.numberOfLeadingZeros(n) - 2;  //Count all "/2" operations
    
    return count;
}

The idea behind this solution is that we don't have to reduce n to 1 to find how many operations are needed to reduce n to 1; in other words, we don't have to show anyone the steps required to reduce to 1, what we really care about are:

  • How many "+1" operations are needed?
  • How many "-1" operations are needed?
  • How many "divided by 2" operations are needed?

To do this, I used some primitive binary operations to create the plus1Operations placeholder, where each bit set to 1 indicates a "+1" operation; after that, I did all the "+1" operations in one go (n += plus1Operations;), thus transforming n into a placeholder where each bit set to 1 indicates a "-1" operation.

And finally, the Hamming weight algorithm is used to count the bits set in n | plus1Operations, while to count the operations "divided by 2", just count how many bits there are to the right of the most significant bit set to 1 of n.

To handle numbers up to 300 digits, just transform this algorithm using the class BigInteger instead of int.

You can find the full explanation of this algorithm here:
https://github.com/parmi93/fuel-injection-perfection

Krasner answered 20/8, 2023 at 18:48 Comment(6)
"time complexity of O(1)": Your code is limited to int input, and not arbitrarily large integers. Asymptotic complexity implies that the input cannot be restricted to int. This function does not work for an input like 123456789123456. Once you turn this into a function that can take a BigInteger, it will not be O(1).Inflatable
@Inflatable The original question by @Single specifies that the maximum size of n is 10^300, so since we don't have to handle arbitrarily large integers, this algorithm has a time complexity of O(1).Krasner
But the point is that your program doesn't work for 10^300, and as for determining the time complexity you are relying on the cap on the input, you're not really giving asymptotic complexity. "Asymptotic" means you don't rely on that. And time complexity is about asymptotic complexity. It is meaningless if you rely on a limit on the input.Inflatable
What I provided is the time complexity bounded by the size of the input. In CS when we talk about complexity by "default" we usually mean in the size of the input, because otherwise not even basic operations such as comparing two variables, shift, addition, etc... cannot be considered operations with a linear time complexity. "But the point is that your program doesn't work for 10^300": if you want you can easily create your own class (like Integer10_300) that supports operations such as right shift, negate, xor, etc.. adapting this algorithm for input of 10^300 with a O(1) time complexityKrasner
"Time complexity bounded by the size of the input": that makes no sense.Inflatable
It makes sense, because otherwise almost no algorithm can be considered with linear time complexity, even a simple printf(int n); There is an interesting discussion about this topic here: cs.stackexchange.com/questions/159696/…Krasner

© 2022 - 2025 — McMap. All rights reserved.