26

Looking for an algorithm or some coding hints to find the solutions for

a^3 + b^3 = c^3 + d^3, where a, b, c and d all are in the range [1 .. 10000]

It's an interview question.

I'm thinking priority queues to at least iterate for a and b values. Some hint will be great, will try to work through from there.

User_67128
  • 1,230
  • 8
  • 21
studying algorithms
  • 525
  • 2
  • 5
  • 13
  • 1
    Which trivial cases are we to eliminate? Obviously all numbers are solutions if a=c and b=d. Also, for every solution a=W,b=X,c=Y,d=Z, there's a solution a=X,b=W,c=Z,d=Y and a solution a=Y,b=Z,c=W,z=X, and so on . – David Schwartz Jan 22 '13 at 08:15
  • 1
    This question probably belongs on http://math.stackexchange.com/ – rink.attendant.6 Jan 22 '13 at 08:22
  • What are your max and min? How can you reduce the solution set recursively? Think `binary search`. – Peter Wood Jan 22 '13 at 08:23
  • 8
    Not an answer, but fun trivia: For `a^3 + b^3 = c^3` there is no solution. This is a private case of [Fermat's Last Theorem](http://en.wikipedia.org/wiki/Fermat%27s_Last_Theorem) – amit Jan 22 '13 at 08:27
  • I have N^2 algo but it used N^2 memory, where N is max limit for numbers. – Толя Jan 22 '13 at 09:03
  • 2
    probably belongs on, but is apparently too old to be migrated to, math.stackexchange.com . – Dan O Feb 18 '17 at 14:23

9 Answers9

24

Using a hash map to store the (cube,(a,b)), you can iterate all possible pairs of integers, and output a solution once you have found that the required sum of cubes is already in the map.

pseudo code:

map <- empty hash_map<int,list<pair<int,int>>>
for each a in range(0,10^5):
  for each b in range(a,10^5): //making sure each pair repeats only once
     cube <- a^3 + b^3
     if map.containsKey(cube):
         for each element e in map.get(cube):
            output e.first(), e.last(), a, b //one solution            
     else:
         map.put(cube,new list<pair<int,int>>)
     //for both cases, add the just found pair to the relevant list
     map.get(cube).add(cube,new pair(a,b))  

This solution is O(n^2) space(1) and O(n^2 + OUTPUT) time on average, where OUTPUT is the size of the output.

EDIT:

Required space is actually O(n^2 logn), where n is the range (10^5), because to represent 10^5 integers you need ceil(log_2(10^15)) = 50 bits. So, you actually need something like 500,000,000,000 bits (+ overhead for map and list) which is ~58.2 GB (+ overhead).

Since for most machines it is a bit too much - you might want to consider storing the data on disk, or if you have 64bits machine - just store in into "memory" and let the OS and virtual memory system do this as best as it can.


(1) As the edit clarifies, it is actually O(n^2log(n)) space, however if we take each integer storage as O(1) (which is usually the case) we get O(n^2) space. Same principle will apply for the time complexity, obviously.

amit
  • 175,853
  • 27
  • 231
  • 333
  • 1
    Nice, needs a well equipped (in memory) computer though :-) (500001^2*sizeof(long long)+hashmap data), or (for a 8 bytes LL) > 2 TB. – Déjà vu Jan 22 '13 at 09:55
  • Required space is actually O(n^2 logn), where n is the range (5,000), because to represent 5000 elements you need ceil(log_2(5000)) bits. So, you actually need something like 325,000,000 bits (+ overhead for map and list) which is ~39 MB – amit Jan 22 '13 at 09:58
  • 4
    where do you get 5'000 from? 10^5 = 100'000. – Gabe Jan 22 '13 at 10:03
  • @Gabe I had a brain fart, I editted it in the answer (the comment is too late now), thanks for mentioning it. – amit Jan 22 '13 at 10:09
  • You can trim down the required space by (about) half if you consider symmetries. Still too much for your average computer, but it gets a little better. Also OUTPUT is Theta(N^2) (consider the obvious cases a=c and b=d) so the n^2 term for time complexity gets outweighed. – Khaur Jan 22 '13 at 10:47
  • 2
    @Khaur Note that the approach ignored the trivial answers (with exception of symetry, it won't return a=c,b=d but it will return a=d,b=c). I totally agree with the symetry issue, can be easily achieved by iterating for `a in range(0,10^5)` and `b in range(a+1,10^5)`. – amit Jan 22 '13 at 10:51
  • @amit Indeed I mentioned the wrong set of trivial answers. I meant a=d and b=c, but they aren't considered anymore with the symmetry improvement. You still need to output them though. – Khaur Jan 22 '13 at 10:57
  • You need to remember that the key is unique so there will be some cases be discarded For example 1^3 + 2^3 = 9 then 1,3 will be added to the map table. But 3 and 1 will not be added due to duplicated key value. Then when you get the result, you need to check if pair[0] != pair[1] then you have to add another case to the final result – NinjaCoder Jul 23 '21 at 11:04
6

Using a priority queue is almost certainly the simplest solution, and also the most practical one, since it's O(n) storage (with a log factor if you require bignums). Any solution which involves computing all possible sums and putting them in a map will require O(n^2) storage, which soon becomes impractical.

My naive, non-optimized implementation using a priority queue is O(n^2 log(n)) time. Even so, it took less than five seconds for n = 10000 and about 750 seconds for n = 100000, using a couple of megabytes of storage. It certainly could be improved.

The basic idea, as per your comment, is to initialize a priority queue with pairs (a, a+1) for a in the range [1, N), and then repeatedly increment the second value of the smallest (by sum of cubes) tuple until it reaches N. If at any time the smallest two elements in the queue are equal, you have a solution. (I could paste the code, but you only asked for a hint.)

rici
  • 234,347
  • 28
  • 237
  • 341
  • so say my max is 7. I initialize the PQ with tuples: (1,1,1), (9,1,2), (28,1,3)...(344,1,7). I pop (1,1,1) and min-heapify the PQ so the next minimum (9,1,2) comes to the top. Now, I start incrementing a (a+1 to max) and b =1 and add it to the PQ but at the same time match (a^3+b^3) to what I've already popped and the min value in the queue. If it matches with either of the two we've a set in hand.. – studying algorithms Jan 25 '13 at 15:31
  • Is my understanding of complexity correct? If n = max, then we'll have n(n-1) elements in the queue (this is maximum set right?) . Min heapifying the queue will cost us O(log(n^2)) ~ O(logn) and since we do this for n^2 elements total cost comes out to be O(n^2 logn) – studying algorithms Jan 25 '13 at 15:36
  • @studyingalgorithms: actually, there are never more than n elements in the queue, because every element has a different `b`. So it really is O(log n), but as you say, that's the same complexity as O(log n^2). (I did a less naive implementation which avoids putting elements into the PQ until necessary, which reduces the maximum size of the PQ to roughly n/4, but that still doesn't affect complexity. It does shave some running time, though.) – rici Jan 25 '13 at 16:39
  • But in this case the space required will be O(n) [size of PQ] and not O(n2)? – studying algorithms Jan 25 '13 at 17:42
  • @studyingalgorithms yes, that's what i said in the answer above. – rici Jan 25 '13 at 17:47
  • @rici Thought of a similar idea, but each item in the priority queue is a column (constant `a`, and a `b` that increments after each time a `a*a*a + b*b*b` sum is pulled out.) Agree priority queue is best approach. – chux - Reinstate Monica Jan 03 '14 at 20:48
4

Using Hashmap (O(n^2) solution):

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import static java.lang.Math.pow;

/**
 * Created by Anup on 10-10-2016.
 */

class Pair {
    int a;
    int b;

    Pair(int x, int y) {
        a = x;
        b = y;
    }
}

public class FindCubePair {
    public static void main(String[] args) {
        HashMap<Long, ArrayList<Pair>> hashMap = new HashMap<>();
        int n = 100000;

        for(int i = 1; i <= n; i++) {
            for(int j = 1; j <= n; j++) {
                long sum = (long) (pow(i, 3) + pow(j, 3));

                if(hashMap.containsKey(sum)) {
                    List<Pair> list = hashMap.get(sum);
                    for(Pair p : list) {
                        System.out.println(i + " " + j + " " + p.a + " " + p.b);
                    }
                } else {
                    ArrayList<Pair> list = new ArrayList<>();
                    hashMap.put(sum, list);
                }

                hashMap.get(sum).add(new Pair(i, j));
            }
        }
    }
}

Unfortunately, the value of integers printed does not even reach 1000 on my computer due to resource limitation.

Anup Verma
  • 183
  • 1
  • 9
  • Why don't you just remove this "hashMap.get(sum).add(new Pair(i, j));" and add this inside the else statement, list.add(new Pair(i,j)); – Kamarudeen Ayankunbi Oct 07 '19 at 17:03
  • Please check the pseudo code provided by @amit. The new Pair needs to be added in both cases. In case the "if" part is run, it simply means the sum already exists in the hashmap for one or more Pair objects, which does not include the new Pair. – Anup Verma Oct 09 '19 at 15:53
2

A quicker than trivial solution is as follows: You calculate all values that a^3 + b^3 can have, and store all possible values of a and b with it. This is done by looping through a and b, storing the results (a^3 + b^3) in a binary tree and having a list of values (a's and b's) associated to each result.

After this step, you need to traverse the list and for each value, choose every possible assignment for a,b,c,d.

I think this solution takes O(n^2 log n) time and O(n^2) space, but i might be missing something.

Gabe
  • 286
  • 1
  • 4
  • Enought only sort this array, without binary tree. – Толя Jan 22 '13 at 09:22
  • You need to search values, as they will get computed several times. Therefore a binary tree is needed. – Gabe Jan 22 '13 at 09:29
  • Search unnecessary, result is a all combinations of pairs with same value. As array sorted all values stored into neighbor indexes. – Толя Jan 22 '13 at 10:05
  • You are right, binary tree was the wrong word. Binary search in a sorted array is absolutely sufficient. – Gabe Jan 22 '13 at 10:19
2
int Search(){
int MAX = 10000000;
for(int a = 0; a < MAX; a++){
    int a3 = a * a * a;
    if(a3 > MAX) break;
    for(int b = a; b < MAX; b ++){
        int b3 = b * b * b;
        if(a3 + b3 > MAX)break;             
        for(int c = 0; c < a; c++){
            int c3 = c*c*c;
            int m = a3 - c3; 
            int d = b+1;
            while(true){
                int d3 = d * d * d;
                if(d3-b3 <= m){
                    if((d3 - b3) == m){
                        count++;
                        PUSH_Modified(a3, b3, c3, b3, a, b, c, d);
                    }
                    d++;
                    continue;
                }
                else
                    break;
            }
        }
    }
}
return 0;

}

1

Let's assume a solution:

a=A, b=B, c=C, and d=D.

Given any solution we can generate another 3 solutions

abcd
ABCD

ABDC
BACD
BADC

Actually, if A=B, or C=D, then we might only have 1 or 2 further solutions.

We can choose the solutions we look for first by ordering A <= B and C <= D. This will reduce the search space. We can generate the missed solutions from the found ones.

There will always be at least one solution, where A=C and B=D. What we're looking for is when A>C and B<D. This comes from the ordering: C can't be greater than A because, as we've chosen to only look at solutions where D>C, the cube sum would be too big.

We can calculate A^3 + B^3, put it in a map as the key, with a vector of pairs A,B as the value.

There will be (n^2)/2 values.

If there are already values in the vector they will all have lower A and they are the solutions we're looking for. We can output them immediately, along with their permutations.

I'm not sure about complexity.

Peter Wood
  • 23,859
  • 5
  • 60
  • 99
  • I've tried this by hand up to `A=5` and there are no results other than the trivial ones. Maybe I'm missing something. – Peter Wood Jan 22 '13 at 14:20
  • 1
    The smallest solution is the Hardy-Ramanujan number 1729 ([1, 12] or [9, 10]). See also "taxicab numbers" (the name comes from the same story); the limit n=100000 will find Ta(2), Ta(3) and Ta(4). – rici Jan 22 '13 at 17:48
  • @rici Thanks, that lead me to find an answer on [math.stackexchange](http://math.stackexchange.com/questions/2815/find-taxicab-numbers-in-on-time). – Peter Wood Jan 22 '13 at 21:32
1

One Solution - using concept of finding 2 sum in a sorted array. This is O(n3)

public static void pairSum() {
    int SZ = 100;
    long[] powArray = new long[SZ];
    for(int i = 0; i< SZ; i++){
        int v = i+1;
        powArray[i] = v*v*v;
    }
    int countPairs = 0;
    int N1 = 0, N2 = SZ-1, N3, N4;
    while(N2 > 0) {
        N1=0;
        while(N2-N1 > 2) {
            long ts = powArray[N1] + powArray[N2];
            N3 = N1+1; N4 = N2-1;
            while(N4 > N3) {
                if(powArray[N4]+powArray[N3] < ts) {
                    N3++;
                }else if(powArray[N4]+powArray[N3] > ts) {
                    N4--;
                }else{
                    //System.out.println((N1+1)+" "+(N2+1)+" "+(N3+1)+" "+(N4+1)+" CUBE "+ts);
                    countPairs++;
                    break;
                }
            }
            N1++;
        }
        N2--;
    }
    System.out.println("quadruplet pair count:"+countPairs);
}
susmit shukla
  • 213
  • 4
  • 14
1

Logic :
a^3 + b^3 = c^3 + d^3
Then, a^3+b^3-c*3-d^3 = 0
Try to solve this equation by putting all combination of values for a,b,c and d in range of [0 , 10^5].
If equation is solved print the values of a,b,c and d

public static void main(String[] args) {

        //find all solutions of a^3 + b^3 = c^3 + d^3
        double power = 3; 
        long counter = 0; // to count the number of solution sets obtained
        int limit = 100000; // range from 0 to limit

        //looping through every combination of a,b,c and d
        for(int a = 0;a<=limit;a++)
        {
            for(int b = 0;b<=limit;b++)
            {
                for(int c = 0;c<=limit;c++)
                {
                    for(int d = 0;d<=limit;d++)
                    {
                        // logic used : a^3 + b^3 = c^3 + d^3 can be written as a^3 + b^3 - c^3 - d^3 = 0
                        long result = (long)(Math.pow(a,power ) + Math.pow(b,power ) - Math.pow(c,power ) - Math.pow(d,power ));
                        if(result == 0 )
                        { 
                            counter++; // to count the number of solutions
                            //printing the solution
                            System.out.println( "a = "+ a + "    b = " + b + "    c = " + c + "    d = " + d);
                        }
                    }
                }
            }
        }
        //just to understand the change in number of solutions as limit and power changes

        System.out.println("Number of Solutions =" + counter);
    }
  • 2
    Please look at [answer]. Code-only answers aren't useful for the community – JimHawkins Jan 19 '17 at 21:27
  • @JimHawkins Thankyou Jim. I am very new here. Joined yesterday only. Still learning the ropes. I did not know how to add code and text, so I explained it in comments in the code. I will try to make this post better. – Swaroop Krishnan S Jan 20 '17 at 16:42
1

Starting with the brute force approach, its pretty obvious it will O(n^4) time to execute. If space is not a constraint, we can go for combination of list and maps. The code is self-explanatory, we are using a nested list to keep track of all entries for a particular sum (key in map). The time complexity is thus reduced from O(n^4) to O(n^2)

public void printAllCubes() {
  int n = 50;
  Map<Integer, ArrayList<ArrayList>> resMap = new HashMap<Integer, ArrayList<ArrayList>>();
  ArrayList pairs = new ArrayList<Integer>();
  ArrayList allPairsList = new ArrayList<ArrayList>();
  for (int c = 1; c < n; c++) {
    for (int d = 1; d < n; d++) {
      int res = (int) (Math.pow(c, 3) + Math.pow(d, 3));
      pairs.add(c);
      pairs.add(d);
      if (resMap.get(res) == null) {
        allPairsList = new ArrayList<ArrayList>();
      } else {
        allPairsList = resMap.get(res);
      }
      allPairsList.add(pairs);
      resMap.put(res, allPairsList);
      pairs = new ArrayList<Integer>();
    }
  }

  for (int a = 1; a < n; a++) {
    for (int b = 1; b < n; b++) {
      int result = (int) (Math.pow(a, 3) + Math.pow(b, 3));
      ArrayList<ArrayList> pairList = resMap.get(result);
      for (List p : pairList) {
        System.out.print(a + " " + b + " ");
        for (Object num : p)
          System.out.print(num + " ");
        System.out.println();
      }
    }
  }
}
ansul90
  • 11
  • 2