minimum number of operations to make two numbers equal

Viewed 5681

I had an interview and couldn't think a clear/best solution for this problem.

Given 2 numbers A and B and we need to convert a number A to B with minimum number of the following operations:

  • Subtract 1
  • Add 1
  • Multiply 2
  • Divide 2
  • Multiply 3
  • Divide 3

For e.g. : if a=3 and b=7, the program should output 2.

1st operation : *2 -> 3*2 = 6.

2nd operation : +1 -> 6 + 1 =7.

For e.g. : if a=10 and b=60, the program should output 2.

1st operation: *2 -> 10*2 = 20.

2nd operation: *3 -> 20*3 = 60

As we can Change m (10) to n (60) after 2 operations, the answer is 2.

Tried to use dynamic programming and recursion approach but to no avail. Any tips?

3 Answers

As mentioned in other answers, this can be approached using BFS in a graph whose nodes correspond to numbers and whose edges correspond to operations.

Interestingly, sometimes, optimal paths need to contain quite large numbers (larger than 3 * max(A, B)). Below is an example of an optimal paths with such large numbers within it:

a = 82, b = 73

optimal path: 
   [82, 164, 328, 656, 657, 219, 73] (6 operations) 

optimal path if paths with values larger than 3 * max(a, b) are discarded: 
   [82, 81, 162, 54, 108, 216, 72, 73] (7 operations)

Below is a python implementation of this BFS solution:

def solve(a, b, max_n=None):
    # the bfs queue
    queue = []

    # length[i] = length of the shortest
    # path to get from `a' to `i'
    length = {}

    # previous[i] = previous value reached
    # in the shortest path from `a' to `i'
    previous = {}

    # node with value `a' is the first in the path
    queue.append(a)
    length[a] = 0
    previous[a] = None

    while True:
        val = queue.pop(0)

        # add an element to the queue (if it was not
        # already visited, and eventually not above
        # some limit)
        def try_add(next_val):
            if max_n is not None and next_val > max_n:
                return
            if next_val in length:
                return
            queue.append(next_val)
            length[next_val] = length[val] + 1
            previous[next_val] = val

        try_add(val + 1)
        try_add(val - 1)

        try_add(val * 2)
        if val % 2 == 0:
            try_add(val // 2)

        try_add(val * 3)
        if val % 3 == 0:
            try_add(val // 3)

        # check whether we already have a solution
        if b in length:
            break

    path = [b]
    while True:
        if path[-1] == a:
            break
        else:
            path.append(previous[path[-1]])

    path.reverse()
    return path


if __name__ == '__main__':
    a = 82
    b = 73
    path = solve(a, b)
    print(len(path), ': ',  path)

    path = solve(a, b, 3 * max(a, b))
    print(len(path), ': ',  path)

Treat numbers as nodes of a graph, and operations as edges. Use BFS to find the shortest path from A to B.

I think you can cap the nodes at 3 times the absolute value of A and B, to minimize the number of steps, but this is not necessary.

The space and time complexity is proportional to the answer, e.g. if the answer is 2, in the worst case we have to visit 6*2=12 nodes.

Here's a BFS Javascript solution:

const findPath = (ops) => (A, B) => {
  const queue = new Set() .add ( [A, []] )
  const paths = new Map()
  while (queue .size !== 0 && !paths .has (B)) {
    const next = [...queue] [0]
    const [n, p] = next
    ops.forEach((fn) => {
      const m = fn(n);
      if (Number.isInteger(m)) {
        if (!paths.has(m)) {
          queue.add([m, [...p, n]])
          paths.set(m, [...p, n])
        }
        queue.delete(next)
      }
    })
  }
  return paths.get(B)
}

const ops = [n => n + 1, n => n - 1, n => 2 * n, n => 3 * n, n => n / 2, n => n / 3]

console .log (
  findPath (ops) (82, 73)
)

We keep a queue of numbers still to process and a dictionary recording the paths for each number found, and keep testing them until the queue is empty (won't happen with these operations, but others might let us drain it) or we've found our target. For each number we run each operation and for integer results add it to our structures if it's not already found.

There is nothing in here to attempt to stop a chain from spiraling out of control. It's not clear how we would do that. And it would clearly be possible with different operations: if we had, say, add 2, subtract 2, and double, we'd never be able to get from 2 to 3. This algorithm would never stop.

While this could of course be converted to a recursive algorithm, the naive recursion is not likely to succeed as it would work depth-first and usually miss the value and never halt.

Related