Learn through the super-clean Baeldung Pro experience:
>> Membership and Baeldung Pro.
No ads, dark-mode and 6 months free of IntelliJ Idea Ultimate to start with.
Last updated: March 18, 2024
In this tutorial, we’ll explain reducing a number to one using the minimum number of operations. The operations to use are to add one, subtract one, and divide by two in case the number is even.
We’ll discuss three approaches to solving the problem and see which approaches provide an optimal answer.
In this problem, we’ll be given a number , and we need to reduce this number to become equal to one using the minimum number of operations.
At each time, we can use one of the following operations:
For example, if is equal to 28, then the optimal solution is using 6 steps:
Also, if is equal to 13, then the optimal solution is using 5 steps:
This greedy approach tries to use the operation that reduces the number as much as possible for the current step. As we know, the number can either be odd or even. If the number is odd, then we can’t use division by 2, and thus we use the operation of subtracting 1.
On the other hand, if the number is even, we can either subtract 1, or divide the number by 2. Since we are following the greedy approach, we choose to divide the number by 2.
For example, if is equal to 10, then the greedy approach will reach 1 after the following 4 steps:
We can prove that this greedy approach doesn’t always provide an optimal solution. A counterexample is if is equal to 15. The greedy approach will give us the following 6 steps:
However, we can find a better solution using only the following 5 steps:
Therefore, we can conclude that this greedy approach is fast, but it doesn’t always give us the optimal solution.
We can find a solution for the problem within the graph theory using the BFS algorithm.
Let’s consider each number to be a node inside the graph. Also, we can consider the edges of the graph to connect the node with nodes
and
. In addition, if
is even, then it’ll be connected to
as well.
Let’s take a look at the implementation of this approach:
algorithm BFSApproach(N):
// INPUT
// N = the number to start from
// OUTPUT
// Returns the minimum number of steps to reach number 1
answer <- an array with 2 * MAX_N elements initialized to infinity
queue <- an empty set
answer[N] <- 0
queue.push(N)
while not queue.empty():
N <- queue.top()
if N = 1:
break
queue.pop()
if N + 1 < 2 * MAX_N and answer[N] + 1 < answer[N + 1]:
answer[N + 1] <- answer[N] + 1
queue.push(N + 1)
if N - 1 > 0 and answer[N] + 1 < answer[N - 1]:
answer[N - 1] <- answer[N] + 1
queue.push(N - 1)
if N mod 2 = 0 and answer[N] + 1 < answer[N / 2]:
answer[N / 2] <- answer[N] + 1
queue.push(N / 2)
return answer[1]
In the beginning, we initialize the array with
, which will hold the number of steps to get to each number starting from
.
Note that the array size should be twice the starting number. The reason is that we may add keep adding 1 to the starting number, but there’s no point in reaching because, from that, we can use division by two and go back to
. Also, we define a queue for the BFS algorithm.
Then, we set the answer of to zero, since it’s the starting number, and push it into the queue. After that, we keep extracting a number
from the queue as long as the queue isn’t empty. For each number, we check whether this number is 1. In this case, we stop the
loop because the algorithm has reached the target number 1.
Otherwise, we check the previously found answer to reach ,
and
. If reaching these numbers from
is better than the way we found before, then we update the
and push the number into the queue.
Note that we make sure that the new number doesn’t exceed and doesn’t drop below 1, to respect the size of the
array. In addition, we only use the operation of division by 2 in case
is even.
Finally, we return , which contains the minimum number of steps to reach the number 1.
The complexity of the BFS algorithm is , where
is the number of vertices and
is the number of edges. In this case, the number of vertices is
, where
is the number to start from. On the other hand, each number can connect to 3 numbers in the worst case. Thus, the number of edges is
.
As a result, the total complexity is , where
is the starting number.
We can update the greedy approach in section 3 to obtain a new optimal one.
Let’s first separate the problem into two cases based on whether is even or odd. If
is even, we can prove that the optimal step is to divide the number by 2.
The reason is that we can either use two addition operations and then a division by 2, which will make us reach which is equal to
using 3 steps. However, we can reach this number by using 2 steps, which are a division by 2 and then adding 1. Therefore, using a total of 2 operations.
Also, we can use two subtraction operations and then a division by 2. In this case, we’ll reach which is equal to
using 3 steps. However, the same number can be obtained by using a division by 2 and then subtracting 1. Thus, using a total of 2 operations only.
In addition, we can eliminate the option of adding and subtracting 1 from , because these two operations negate each other. Therefore, if
is even, then it’s always optimal to divide it by 2.
On the other hand, if is odd, then we need to consider multiple cases. Let’s consider the least 2 significant bits of
in case
is odd. We have 2 cases
and
(The
represents the rest of the number). We can use the fact that an even number
has the optimal choice of dividing by 2 to reach
.
In the case of :
As a result, we see that subtraction always gives a better or equal answer to adding in the case of numbers that end with in their binary representation.
The other case is :
As we can see, using the operation of adding 1 is always better in the case of numbers that end with in their binary representation.
We can conclude this analysis of odd numbers by checking the modular after dividing the number by 4; if it’s a 1 then we decide to subtract. Otherwise, we add 1. The only exception to this is if is equal to 3, in which case we need to do two subtractions.
Take a look at the implementation of this approach:
algorithm OptimalGreedyApproach(N):
// INPUT
// N = the number to start from
// OUTPUT
// the minimum number of steps to reach number 1
answer <- 0
while N > 1:
if N mod 2 = 0:
N <- N / 2
else if N = 3 or N mod 4 = 1:
N <- N - 1
else:
N <- N + 1
answer <- answer + 1
return answer
The implementation is similar to the theoretical idea discussed in section 5.1. In case is even, it’s always better to use a division operation. Otherwise, if
is either equal to 3, or has a modular of 1 after dividing by 4, then we subtract. In other cases, we use the adding operation.
The complexity of the optimal greedy approach is , where
is the initial number. The reason is that even if we used subtraction or addition, both operations would lead to an even number. Therefore, the division by two operations occurs at least once every two steps.
In this article, we discussed reducing a number into 1 by using the operations of either dividing by 2 or adding or subtracting 1.
We discussed three approaches to solve the problem and then explained which approaches give the optimal answer.