Minimum moves to reach k

Prepcastdoom :

Given two numbers m and n, in one move you can get two new pairs:

  • m+n, n
  • m, n+m

Let's intially set m = n = 1 find the minimum number of moves so that at least one of the numbers equals k

it's guaranteed there's a solution (i.e. there exist a sequence of moves that leads to k)

For example: given k = 5 the minimum number of moves so that m or n is equal to k is 3

1, 1
1, 2
3, 2
3, 5

Total of 3 moves.

I have come up with a solution using recursion in python, but it doesn't seem to work on big number (i.e 10^6)

def calc(m, n, k):
    if n > k or m > k:
        return 10**6
    elif n == k or m == k:
        return 0
    else:
        return min(1+calc(m+n, n, k), 1+calc(m, m+n, k))

k = int(input())
print(calc(1, 1, k))

How can I improve the performance so it works for big numbers?

DarrylG :

Non-Recursive Algorithm based on Priority Queue (using Heap)

State: (sum_, m, n, path)
    sum_ is current sum (i.e. m + n)
    m and n are the first and second numbers
    path is the sequence of (m, n) pairs to get to the current sum

In each step there are two possible moves

  1. Replace first number by the sum
  2. Replace second number by the sum

Thus each state generates two new states. States are prioritized by:

  1. moves: states with a lower number of have higher priority
  2. sum: States with higher sums have higher priority

We use a Priority Queue (Heap in this case) to process states by priority.

Code

def calc1(k):
  if k < 1:
    return None, None  # No solution

  m, n, moves = 1, 1, 0
  if m == k or n == k:
    return moves, [(m, n)]

  h = []  # Priority queue (heap)

  path = [(m, n)]
  sum_ = m + n
  # Python's heapq acts as a min queue.
  # We can order thing by max by using -value rather than value
  # Thus Tuple (moves+1, -sum_, ...) prioritizes by 1) min moves, and 2) max sum
  heappush(h, (moves+1, -sum_, sum_, n, path))
  heappush(h, (moves+1, -sum_, m, sum_, path))

  while h:
    # Get state with lowest sum
    moves, sum_, m, n, path = heappop(h)

    sum_ = - sum_

    if sum_ == k:
      return moves, path  # Found solution

    if sum_ < k:
      sum_ = m + n  # new sum
      # Replace first number with sum
      heappush(h, (moves+1, -sum_, sum_, n, path + [(sum_, n)]))
      # Replace second number with sum
      heappush(h, (moves+1, -sum_, m, sum_, path + [(m, sum_)]))

    # else:
    #  so just continues since sum_ > k

  # Exhausted all options, so no solution
  return None, None

Test

Test Code

for k in [5, 100, 1000]:
  moves, path = calc1(k)
  print(f'k: {k}, Moves: {moves}, Path: {path}')

Output

k: 5, Moves: 3, Path: [(1, 1), (2, 3), (2, 5)]
k: 100, Moves: 10, Path: [(1, 1), (2, 3), (5, 3), (8, 3), (8, 11),
                         (8, 19), (27, 19), (27, 46), (27, 73), (27, 100)]
k: 1000, Moves: 15, Path: [(1, 1), (2, 3), (5, 3), (8, 3), (8, 11),
                          (19, 11), (19, 30), (49, 30), (79, 30), (79, 109), 
                          (188, 109), (297, 109), (297, 406), (297, 703), (297, 1000)]

Performance Improvement

Following two adjustments to improve performance

  1. Not including path just number of steps (providing 3X speedup for k = 10,000
  2. Not using symmetric pairs (provided 2x additional with k = 10, 000

By symmetric pairs, mean pairs of m, n which are the same forward and backwards, such as (1, 2) and (2, 1).
We don't need to branch on both of these since they will provide the same solution step count.

Improved Code

def calc(k):
  if k < 1:
    return None, None

  m, n, moves = 1, 1, 0
  if m == k or n == k:
    return moves

  h = []    # Priority queue (heap)

  sum_ = m + n
  heappush(h, (moves+1, -sum_, sum_, n))

  while h:
    moves, sum_, m, n = heappop(h)
    sum_ = - sum_

    if sum_ == k:
      return moves

    if sum_ < k:
      sum_ = m + n
      steps = [(sum_, n), (m, sum_)]
      heappush(h, (moves+1, -sum_, *steps[0]))
      if steps[0] != steps[-1]: # not same tuple in reverse (i.e. not symmetric)
        heappush(h, (moves+1, -sum_, *steps[1]))

Performance

Tested up to k = 100, 000 which took ~2 minutes.

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=408164&siteId=1
Recommended