Archive for the ‘Puzzles’ Category

Proving There are Only Six Dudeney Numbers

Thursday, December 24th, 2009

I came across an article in Wikipedia about Dudeney numbers. These are numbers whose digit sum add up to their cube root:

    1 =  1 x  1 x  1   ;   1 = 1
  512 =  8 x  8 x  8   ;   8 = 5 + 1 + 2
 4913 = 17 x 17 x 17   ;  17 = 4 + 9 + 1 + 3
 5832 = 18 x 18 x 18   ;  18 = 5 + 8 + 3 + 2
17576 = 26 x 26 x 26   ;  26 = 1 + 7 + 5 + 7 + 6
19683 = 27 x 27 x 27   ;  27 = 1 + 9 + 6 + 8 + 3

The wiki page went on to proclaim that those are the only six such numbers. Somebody on the talk page asked where the proof was.

I poked on Google and didn’t find anything, so I wondered if I could just prove it myself. Here’s what I came up with off the cuff. Perhaps others would find it interesting.

(more…)

Finding the Three Fastest Laptops

Wednesday, April 16th, 2008

I somehow tripped across this problem on something called “Monday Math Madness #4″. They mentioned a Rubik’s Cube as a prize, and since some of the stickers are coming off of mine I decided to go ahead and work out the solution. My answer was right, but there were many correct answers so I was not chosen to win the cube… or the Amazon gift certificate. Alas.

Daniel: Hey Quan, we just received a shipment of 25 Macbook Pros from Apple Inc.

Quan: WOWIE PAZOWIE! What are we going to do with 25 Macbook Pros?

Daniel: Let’s keep 3 of them for personal use and give the other 22 away to the winner of this contest.

Quan: Okay. But personally, I think the winner would rather prefer a 10 dollar GC to amazon.

Daniel: You’re probably right. In that case, we’ll just take a sledgehammer and smash the other 22 laptops to oblivion. Anyhow, since all Macbook Pros are not created equally, let’s determine which 3 Macbook Pros are the fastest. Those are the one’s that we’ll keep.

Quan: Good idea. We can go to the computer lab and run a simple test.

Daniel: Right. We can hook them up to the blinkdagger station and have each laptop process a special algorithm that I developed in MATLAB.

Quan: But we can only hook up 5 Macbook Pros at one time. And the station only ranks the 5 laptops from 1 (fastest) to 5 (slowest). It doesn’t give us any other information. This could potentially take forever!

Daniel: Don’t worry Quan. The optimal strategy will only take ______ iterations.


First test all laptop groups w/no overlap (5 iterations). Label each computer with its performance rank order in the test.

Remembering which group it came from, test the fastest laptops from each group (marked 1) against each other. This will be the sixth iteration. Label each computer in the group whose laptop won this test “A”, the group whose laptop came in second place “B”, etc.

So each computer should now have a letter and a number. These equations show us what we know so far about their relative speeds:

A1 < A2 < A3 < A4 < A5
B1 < B2 < B3 < B4 < B5
C1 < C2 < C3 < C4 < C5
D1 < D2 < D3 < D4 < D5
E1 < E2 < E3 < E4 < E5
…and…
A1 < B1 < C1 < D1 < E1

We are only looking for 3 laptops. Thus we can exclude all computers labeled D and E as well as those labeled 4 and 5, because there are always three computers guaranteed to be faster than them. So this reduces our consideration to:

A1 < A2 < A3
B1 < B2 < B3
C1 < C2 < C3
…and…
A1 < B1 < C1

By similar logic, we know that B3, C2, and C3 are not in the running…e.g. we cannot choose B3 because the transitive property tells us that B2, B1, and A1 are faster. Narrowing the field to potential winners for the top 3 places we now have:

A1 < A2 < A3
B1 < B2
C1
…and…
A1 < B1 < C1

It is certain that A1 will be chosen, because it is the fastest of the fast. That leaves only 5 candidates for second and third place. So test them all together for the seventh and final iteration [A2, A3, B1, B2, C1]. Take the two top winners from this test along with A1 and you are done.

That is 7 iterations.

Sums of numbers that add to X

Sunday, October 10th, 2004

I saw the following coding problem:

Write a function that takes 4 integers as input and returns an integer. The four input values will each range from 1-10, and the function should calculate the number of combinations of input that add to 15 and return that number of combinations.

For example, if the four input values were labeled A-D and had the values 5, 5, 5 and 10 there would be 4 combinations adding to 15:

Input:
A = 5
B = 5
C = 5
D = 10

Combos:
A+D = 15
B+D = 15
C+D = 15
A+B+C = 15

Desired output: 4

Please describe how you would modify your solution to accept N input values (instead of just four).

My first try turned out to be slower than a solution from an experienced C++ programmer who’d leveraged the C++ standard library algorithms:

class Knapsack
{
public:
  Knapsack (std::vector::const_iterator beg,
      std::vector::const_iterator end,
      int sum)
    : _pieces (beg, end), _sum (sum)
    {
        // precondition: input was sorted
    }
  int UniqueCombinations ();
private:
  std::vector _pieces;
  int _sum;
};
 
// Each combination is counted only once
// Hint: If a pick contains several equal items in a row,
// they were all picked from the front of a run of
// equal items in the (sorted) input (see the skipping below)
int Knapsack::UniqueCombinations ()
{
  int count = 0;
  std::vector::const_iterator it = _pieces.begin ();
  std::vector::const_iterator end = _pieces.end ();
  assert (it != end);    
  do
  {
     int curItem = *it;
     int newSum = _sum - curItem;
     ++it;
     if (newSum == 0)
     {
       ++count;
     }
     else if (newSum > 0 && it != end)
     {
       Knapsack smallerKnapsack (it, end, newSum);
       count += smallerKnapsack.UniqueCombinations ();
     }
     // skip all equal value items
     while (it != end && *it == curItem)
       ++it;
    } while (it != end);
  return count;
}
 
int FillKnapsack (int const * arr, int size, int sum)
{
  if (size == 0)
  {
    std::cout << "0 combinations: empty array" << std::endl;
    return 0;
  }
 
  try
  {
    std::vector data (arr, arr + size);
    // sort in decreasing order
    std::sort (data.begin (), data.end (), std::greater ());
    Knapsack knapsack (data.begin (), data.end (), sum);
    int comb = knapsack.UniqueCombinations ();
 
    std::cout << comb << " combination(s)" << std::endl;
    return comb;
  }
  catch (std::bad_alloc)
  {
    std::cerr << "Out of memory" << std::endl;
  }
  catch (...)
  {
    std::cerr << "Unknown error" << std::endl;
  }
  return 0;
}

Not to be outdone, I started wondering if there was a way to do it even faster. Since the values in the array are bounded, I decided to immediately transform the input into a histogram telling how many of each number appeared. Then I would do a brute force march through all the histograms that were “recipes” adding to the target sum, and add the appropriate number of sums for each match. So the sample case:

A = 5
B = 5
C = 5
D = 10

I’d quickly transform it to:

[0,0,0,0,3,0,0,0,0,1] // three fives and one 10

Then, I’d generate the exhaustive list of “recipes” that sum to 15:

[15,0,0,0,0,0,0,0,0,0] // fifteen ones
[13,1,0,0,0,0,0,0,0,0] // thirteen ones and one two
[12,0,1,0,0,0,0,0,0,0] // twelve ones and one three

[0,0,0,0,1,0,0,0,0,1] // one ten and one five

From there it was just a matter of seeing how many times a recipe could be applied on the input. That’s relatively straightforward combinatorics, but my example cases on large data sets overflowed my naive algorithm for combinations. It was a hard bug to track down, but once I replaced it with a better routine off the web I had a blazing fast algorithm. Its performance is determined by the range of the numbers and the value of the sum, not the size of the input array.

// combinations
// how many combinations of k objects can you choose from a set of n objects ?
// this method avoids overflow that you hit with:
//    factorial(n) / (factorial(k) * factorial(n-k))
// see http://www.cs.sjsu.edu/faculty/fecteau/cs140/combPerm.html
unsigned int combinations(unsigned int r, unsigned int n)
  {
  // Assert(n >= k);
  unsigned int k = r < n-r ? r : n-r;
  unsigned int answer = 1;
  unsigned int multiplier = n;
  unsigned int divisor = 1;
  while (divisor <= k)
    {
    answer = ( answer * multiplier ) / divisor; // this will evenly divide 
    multiplier--;
    divisor++;
    }
  return answer;
  }
 
// SumsRecipe
//
// Given a "recipe" for producing a certain sum, tell how many ways that recipe
// can be achieved given the set of numbers indicated by Nums.  Both the recipe
// and the input list are set up so that array[j] tells you how many j's you have
// (or in the case of the recipe, how many j's are needed)
//
unsigned int SumsRecipe(const unsigned int Nums[],
    const unsigned int Recipe[],
    unsigned int Max)
  {
  unsigned int Sums = 1;
  for (unsigned int j = 1; j <= Max; j++)
    {
    if (Nums[j] < Recipe[j])
      return 0;
    if (Recipe[j] != 0)
      Sums *= combinations(Recipe[j], Nums[j]);
    }
  return Sums;
  }
 
// EnumRecipe
//
// Class which provides a somewhat brute-force method of enumerating over all
// the possible "Recipes" for making the value X as a sum of numbers 1-Max.
//
// It really just ticks through the recipes like an odometer, trying:
//   1 one, then 2 ones, then 3 ones...
// When it hits the maximum number of ones you can have it then tries:
//   1 two with 0 ones, then 1 two with 1 one, then 1 two with 2 ones...
//
// Although it might seem like this is a terrible way to generate these
// recipes, there are a couple of "saving graces".  We know
// we don't have more than Length numbers available, and that the total
// can't be greater than X...so if we hit an excess of either of these
// conditions we can fast-forward the "odometer" to the next setting
// which zeroes one of the wheels.
//
class EnumRecipe
  {
private:
  unsigned int m_X; // Target value for our recipe's sum
  unsigned int m_Length; // Most numbers that could occur in a recipe
  unsigned int m_Max; // Maximum value of any given number
  unsigned int m_Used; // Numbers used in this recipe
  unsigned int m_Total; // Current running total sum
  bool m_First; // First time calling the enumerator?
  const unsigned int* m_Nums; // How many of each number is available in the input?
 
public:
  EnumRecipe(unsigned int X,  
      const unsigned int* Nums, 
      unsigned int Length, 
      unsigned int Max) :
    m_X(X),
    m_Nums(Nums),
    m_Length(Length),
    m_Max(Max),
    m_Used(0),
    m_Total(0),
    m_First(true)
    { // Nothing to do
    }
 
  bool NextRecipe(unsigned int Recipe[])
    {
    if (m_First)
      // since this is the first call, start by zeroing out
      // the recipe that the user passed in...
      {
      for (unsigned int j = 0; j <= m_Max; j++)
        Recipe[j] = 0;
      m_First = false;
      // Assert(m_Total == 0);
      // Assert(m_Used == 0);
      }
 
    unsigned int Wheel = 1;
    while (true)
      {
      // Will increasing the current "Wheel" lead to a
      // "Recipe" that uses more values than we have as
      // input, or one that the recipe's summation will 
      // exceed our target X value?
      if ((m_Used < m_Length) && 
          (m_Total < m_X) && 
          (m_Nums[Wheel] >= Recipe[Wheel]))
        {
        Recipe[Wheel]++;
        m_Total += Wheel;
        m_Used++;
 
        if (m_Total == m_X)
          return true;
 
        // as on an odometer, each time we successfully
        // increment one of the wheels, the next wheel
        // we'll try to increment will be the first again
        Wheel = 1;
        }
      else
        // We reached the maximum value for this "wheel"
        {
        // Is it the last one?  If so, we're done.
        if (Wheel == m_Max)
          return false;
 
        // Zero this wheel and update our state variables.
        // This intermediate state is one that we've visited
        // before, so no need to test total against m_X
       m_Total -= (Wheel * Recipe[Wheel]);
       m_Used -= Recipe[Wheel];
       Recipe[Wheel] = 0;
       Wheel++;
        }
      }
 
    return true;
    }
 
  virtual ~EnumRecipe()
    { // Nothing to do
    }
  };
 
// SumsThatAddToXSpecialized
//
// Specialized version of code which answers the "how many ways
// are there to achieve the sum of X using the integers in Values[]".
// This case is specialized because it assumes that the
// values in the array range from 1..Max.  What's different about
// this particular approach is that the performance is not a
// function of the length of the input array.
//
unsigned int SumsThatAddToXSpecialized(
    unsigned int X, 
    const unsigned int Values[], 
    unsigned int Length,
    unsigned int Max)
  {
  unsigned int Nums[Max+1];
  for (unsigned int j = 0; j <= Max; j++)
    Nums[j] = 0;
  for (unsigned int i = 0; i < Length; i++)
    {
    // Assert((Values[i] <= Max) && (Values[i] > 0));
    Nums[Values[i]]++;
    }
 
  unsigned int Sums = 0;
 
  unsigned int Recipe[Max+1];
  EnumRecipe er (X, Nums, Length, Max);
  while (er.NextRecipe(Recipe))
    Sums += SumsRecipe(Nums, Recipe, Max);
 
  return Sums;
  }

It’s fun to see something like this run so fast and still be correct!


Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported
Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported