17

Here's the description of this problem:

You are given two integers a and b. You want to find the shortest sequence of operations necessary to transform a into b, where at each step you are allowed to add or subtract 5, 7, or 12.

For example, if you are given a = -5 and b = 19, the shortest path is

-5 + 12 + 12 = 19

If you were given 1 and 3, the shortest path would be

1 + 7 - 5 = 2

The only way I can think about solving this is using BFS and maybe some more pruning. Is there a better algorithm I could use instead?

Thanks!

Ivan
  • 766
  • 6
  • 17

6 Answers6

32

Let's start off with a set of interesting observations. As many others have noted, the goal is to find some linear combination 5x + 7y + 12z = b - a with integer coefficients such that |x| + |y| + |z| is minimized. But there are some very interesting connections between these three numbers that we can exploit:

  1. If we ever have a combination 5x + 7y + 12z where x and y are both positive or both negative, we can cancel out some number of x's and y's to add an equivalent number of 12s. In other words, no optimal solution has the same sign on both x and y, because we could always make this solution better.
  2. If we ever have a combination 5x + 7y + 12z where y and z have opposite signs, we can always remove a 7 and 12 and add in a 5 of the appropriate sign to get a better solution. Similarly, if x and z have opposite signs, we can always remove a 5 and 12 and add a 7 of the appropriate sign. This means that we never need to consider any solution where z has the same sign as either x or y, because it means that there would have to be a better solution.

Let's think about what (1) and (2) collectively tell us. (1) says that the signs on x and y can't be the same, since we can always do better. (2) says that if x and z have opposite signs or if y and z have opposite signs, we can always do better. Collectively this means that

Lemma: At least one of x, y, or z must be zero in the optimal solution.

To see this, if all three are nonzero, if x and y have the same sign, then we can clearly make the solution better by replacing them with 12s. Otherwise, x and y have opposite signs. Thus if x and z have different signs, by (2) we can replace them with fewer 7's, otherwise y and z have different signs and by (2) we can replace them with fewer 5's.

Okay, this is looking really great! This means that we just need to solve these three integer equations and find which one has the smallest sum of coefficients:

  • 5x + 7y = b - a
  • 5x + 12z = b - a
  • 7y + 12z = b - a

Fortunately, by Bezout's identity, because gcd(5, 7) = gcd(5, 12) = gcd(7, 12) = 1, all of these systems of equations have a solution for any value of b - a.

Now, let's see how to solve each of these equations. Fortunately, we can use some cute tricks to greatly reduce our search space. For example, for 5x + 7y = b - a, the value of x can't be outside of [-6, +6], since if it were we could just replace seven of the 5's with five 7's. This means that we can solve the above equation by doing the following:

For x = -6 to +6, see if 5x + 7y = b - a has an integer solution by computing (b - a) - 5x and seeing if it's divisible by seven. If so, the number of steps required to solve the problem is given by |x| + |((b - a) - 5x) / 7|.

We can use similar tricks to solve the latter two equations - for the second equation, x ranges from -11 to +11, and for the third y ranges from -11 to +11 as well. We can then just take the best answer out of all three equations to see what the answer is.

Here's some pseudocode to record the fewest number of steps possible. This can easily be modified to return what those steps are by just recording which of the solutions was used and then expanding it out into a full path:

Let best = infinity

# Solve 5x + 7y = b - a
for x = -6 to +6:
    if ((b - a) - 5 * x) mod 7 = 0:
        best = min(best, |x| + |((b - a) - 5 * x) / 7|)

# Solve 5x + 12y = b - a
for x = -11 to +11:
    if ((b - a) - 5 * x) mod 12 = 0:
        best = min(best, |x| + |((b - a) - 5 * x) / 12|)

# Solve 7x + 12y = b - a
for x = -11 to +11:
    if ((b - a) - 7 * x) mod 12 = 0:
        best = min(best, |x| + |((b - a) - 7 * x) / 12|)

return best;

This algorithm is amazingly fast - it runs in O(1) time because the number of iterations required to solve each three of the linear systems is a constant (at most 23). It requires only O(1) memory to hold the possible values, and I think that in practice it's probably the fastest algorithm you'll be able to write.

Hope this helps!

templatetypedef
  • 362,284
  • 104
  • 897
  • 1,065
  • I wonder if the problem was set up to need those tricks. @Ivan didn't say where his problem comes from. – toto2 Sep 02 '11 at 18:11
  • Correct me if I'm wrong, but wouldn't that return `min(x,y,z)` instead of `|x|+|y|+|z|` for a solution of `5x + 7y + 12z = b - a` ? – harold Sep 02 '11 at 18:49
  • @harold- I don't believe so. Notice that each of the times that we try setting `best` to be some new value, it's always equal to the current value of `|x|` plus the matching value of `|y|` that we'd need for the particular linear equation. It's possible that I have a mistake here, though. Can you elaborate on what you think is wrong? I'd be happy to fix things if they're broken. :-) – templatetypedef Sep 02 '11 at 19:00
  • Well, I didn't account for the fact that one of `x` `y` and `z` is zero. That could be it. But what I thought what was happening was that `best` will never go higher than `|x|` (referring to the `x` from the linear equation), because it is done first and `best` is only ever modifier through a `min` with itself. – harold Sep 02 '11 at 19:15
  • @harold- Hmmm... I don't think that `best` is ever set just to `|x|`; it's always set to the sum of `|x|` and the coefficient of the other value in the equation that would be needed to sum up to the target. I'm abusing notation a bit by using the fact that since one coefficient always must be zero, we can just relabel the two variables actually being used to be `x` and `y`. Does that clarify things? – templatetypedef Sep 02 '11 at 19:17
  • Why not using bezout for solving any x,y for instnace For solving 5x+7y=n, you should apply x,y as (3*n+k*7,-2*n+k*5) and then run k from -6 to 6 to find min(abs(x)+abs(y)). – Luka Rahne Sep 02 '11 at 19:50
  • @ralu- That's a good point. I haven't used that approach in a while and couldn't find much documentation online that would prove that it would work correctly. However, since the range of iteration ends up being the same, I don't think the approach will be that much faster than what I have posted. Is there a good reference I could consult to learn more about how to do this? – templatetypedef Sep 02 '11 at 19:53
  • You posted link how to crack this. Since gcd() is always 1 you just have to multiply solutions by n and apply same "offset" as described in article. You can see that 3 and -2 form equation posted before are just values from extended euclid -gcd. Than you transform this in finding minimum once you know which sign will x and y have. Insted of going from -12 to 12 you can test like 2 or 4 options. – Luka Rahne Sep 02 '11 at 20:17
  • Very nice algorithm. If you want to start attacking the constants in that O(1), you could start by iterating over y instead of x. Then your loops go from -4 to 4, -4 to 4, and -6 to 6 respectively. You know, if you needed to run this a billion times. – Aubrey da Cunha Sep 02 '11 at 23:04
  • @Aubrey de Cunha- I don't believe that you can make that change, since the reason you can iterate over the smaller value is that you can replace many copies of the small number with a few copies of the larger number. If you do the opposite, you introduce many more copies of the small number. – templatetypedef Sep 02 '11 at 23:33
  • Thanks for the detail explantion.That's really a math problem rather that an algrithom. :) You make me think a litter ahead. In this problem the three operation number(5,7,12) is quitely related. You reduce the complexity using this relation. If we summary the process to general case, it would looks like `1. The precondition that exists solution about a1x1 + a2x2 +…+ anxn = c,( a1,a2,…an,c∈N ) is gcd( a1,a2,…an ) | c , give a initial value, and find a range about each coefficient , and finally find the smallest one by comparing` – Ivan Sep 03 '11 at 05:40
  • However, it looks like you can at least cut the range for x from -(x+y)/2 to (x+y)/2. Otherwise, you can replace a x's with x y's and (a-y) x's. If a>(x+y)/2, then x+|a-y| – Aubrey da Cunha Sep 03 '11 at 17:54
2

The problem is equivalent to getting the number a-b. Or abs(a-b), since it's symmetric around zero. I think this can be done easily with an adoption of dynamic programming. For example, to quickest way to get 23 is the quickest way to get 23+5,23-5,23+7,23-7,23+12 or 23-12 plus one operation. If you apply that a couple of times on the start condition (cost of +5,-5,.. is 1, others are infinite), you'll have your answer in O(a-b).

Ishtar
  • 11,542
  • 1
  • 25
  • 31
  • I just gave the same answer and deleted. Think it carefully and you will find it's the same as the BFS appproch. – Mu Qiao Sep 02 '11 at 16:12
  • @Mu Qiao - Please explain. I don't see how this is exactly BFS. – Ishtar Sep 02 '11 at 16:21
  • You need to calculate 23+5,23-5,23+7,23-7,23+12 and 23-12. And then continue -5, +5, -12, +12.....right? The result value might be overlapped and what you are doing is exactly the same as BFS. – Mu Qiao Sep 02 '11 at 16:25
  • The nodes in the graph are numbers. There are edges between numbers that are +- 5, +- 7, and +- 12. – Rob Neuhaus Sep 02 '11 at 16:25
  • If you leave out memoization yes - or more accurately, maybe. You could also find the answer with DFS and cycle detection, not that I would recommend it. But memoization is a key element of DP, and makes it "not a tree search" because the search"tree" is really a search graph. – harold Sep 02 '11 at 16:31
  • @harold I don't know if you agree with this answer. But memoization should cache something that doesn't change. But this approach needs to update the values that have been calculated. – Mu Qiao Sep 02 '11 at 16:46
  • @Mu Qiao: it would be more elegant if it wouldn't have to do that, but IMO that does not disqualify it from being DP. – harold Sep 02 '11 at 17:39
2

You need to solve 5x+7y+12z = b-a. such that |x| + |y| + |z| is minimum. Perhaps there is a straightforward mathematical way. Perhaps this helps: Link

Glorfindel
  • 21,988
  • 13
  • 81
  • 109
  • How would you solve this without brute-force without a "system" of equations to do substitution? – Louis Ricci Sep 02 '11 at 17:37
  • You can solve this class of problems with [integer linear programming](http://en.wikipedia.org/wiki/Integer_linear_programming#Integer_unknowns). Unfortunately it's NP-hard in the general case, but there are good approximation algorithms, and you could find exact solutions on small problems like this. – japreiss Sep 02 '11 at 18:17
0

I guess you could have a look at the Subset Sum Problem for ideas.

Tony The Lion
  • 61,704
  • 67
  • 242
  • 415
0

Pre calculate the operations needed for the first minimum range, after that just keep adding multiples of +12.

aero
  • 262
  • 2
  • 10
-1

Pre-calculate all your operations combos into a hash map then just do a look-up on the answer.

The pre-calculation step will take time but once its done you have finds for answers within the pre-calculated range that are 1 look-up operation.

Here is a small JavaScript demonstration:

// maximum depth of combos to try
var MAX = 6;
// possible operations
var ops = ["+-5", "+5", "+-7", "+7", "+-12", "+12"];
// initial hash map of operations->value
var all = {"+5":5, "+-5":-5, "+7":7, "+-7":-7, "+12":12, "+-12":-12};
var allcnt = 6; // count combos *not needed*
// initial hash map of values->operations, plus "0" so we can avoid it
var unique = {"0": "0", "5":"+5", "-5":"+-5", "7":"+7", "-7":"+-7", "12":"+12", "-12":"+-12" };
var ready = false;
// get all useful combinations of ops
function precalc() {
  var items = [];
  for(var p in all) {
     items.push(p);
  }
  for(var p=0; p<items.length; p++) {
    for(var i=0; i<ops.length; i++) {
      var k = items[p] + ops[i];
      var v = eval(k);
      if(unique[v] == null) {
        unique[v] = k;
        all[k] = v;
        allcnt++;
      }
    }
  }
}
// find an answer within the pre-calc'd depth
function find(a, b) {
  if(ready === false) {
    for(var i=0; i<MAX; i++) precalc();
    ready = true;
  }
  return unique[""+Math.abs(a-b)];
}
// test
find(-5,19);
Louis Ricci
  • 20,804
  • 5
  • 48
  • 62