21

I recently participated in a competition where I was asked this question. Given an array with lengths what is the area of the biggest rectangle that can be made using ALL the lengths. The lengths can be added but not broken in between.

Example: [ 4,2,4,4,6,8 ] given this array the best we can do is make a rectangle of sides 8 and 6 like this.

enter image description here

giving an area of 8 * 6 = 48.

I am a beginner and even after a long hard think about how to do it I am unable to get anywhere. I am not looking for a solution but any clue to nudge me in the right direction would be appreciated.

TIA

Edit: Somebody pointed out(comment deleted now) that its difficult to explain the solution with just hints and not posting some code. Kindly post code if necessary.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
johne
  • 391
  • 3
  • 8
  • 1
    Use codegolf.stackexchange.com – Hans Passant Sep 03 '11 at 17:06
  • 13
    I think this is a fine question asking for a possible algorithm to solve this problem. Voting to reopen. – Howard Sep 03 '11 at 17:42
  • i am being voted off codegolf too! apparently my question is not appropriate there either. can someone suggest where to get help. I am sorry if this place is inappropriate but one more suggestion like codegolf would be helpful. – johne Sep 03 '11 at 18:06
  • +1 reopen. The question is offtopic and not welcome on Code Golf, where you perform competitions; it would be unusual to perform a question, you don't know the answer for (while not impossible). But the format fits well here, I think. – user unknown Sep 03 '11 at 18:20
  • I'm sure if this was tagged 'interview-question' it would have received much more attention. +1 and vote for reopen – BlackBear Sep 03 '11 at 19:24
  • This seems related to the partition problem, which is known to be NP-hard. Perhaps there's a good heuristic? – templatetypedef Sep 03 '11 at 19:37
  • 3
    How many lengths are we supposed to deal with in the worst case? How big can the sticks be? Having a bound on the big-O complexity helps the problem-solving (and the problem statement at a competition should include this kind of info). – hugomg Sep 03 '11 at 19:39
  • I do not remember the exact wording of the puzzle but this is the exact same example given in the question. From what I remember of the other examples its reasonable to assume for the purposes of this discussion that about 10 sticks of lengths ranging from 1 to 100 can be used. And there was no upper bound given with the question. I hope this information is sufficient. – johne Sep 03 '11 at 19:53
  • I can think the practical importance of this question. The no of elements = no of concrete blocks. Considering each elements as the length of the individual concrete blocks. Then now the question is to wisely constructing a fencing, protecting maximum area from any external disaster. – Muthu Ganapathy Nathan Sep 03 '11 at 19:56
  • @John can u give me the solution for this case, what if my input is [2 3 1 5 8 9]? since, i cannot form a rectangle itself with is array.Explanation from anyone is most welcome. – Muthu Ganapathy Nathan Sep 03 '11 at 20:15
  • @eager_student: in case a rectangle cannot be formed an output of 0 would suffice. – johne Sep 03 '11 at 20:17
  • @EAGER_STUDENT for [2 3 5 1 8 9] you get [2 3] [5] [1 8] [9]. All nice and sorted. :) – vhallac Sep 03 '11 at 21:06
  • @vhallac oh!its great!this solution has changed my logic! nice – Muthu Ganapathy Nathan Sep 04 '11 at 03:32
  • Although EAGER_STUDENT's case turned out to be solvable, in general there are plainly unsolvable cases -- e.g. [1 2 3 4]. – j_random_hacker Sep 04 '11 at 04:50
  • This problem is NP-Complete but not in strong sense, means if the input size (numbers like 2,4,8,6,5 which used to create a sides of rectangle) is not big this can be done in polynomial time, in fact the solution of `S*n` where S is some of line segment length and `n` is number of inputs available, in fact you can use dynamic programming for this. see wiki pseudo polynomial time algorithm for partition. – Saeed Amiri Nov 07 '11 at 20:19

3 Answers3

11

The problem is NP-Hard, thus the backtracking solution [or other exponential solution as suggested by @vhallac] will be your best shot, since there is not known [and if P!=NP, there is no existing] polynomial solution for this kind of problem.

NP-Hardness proof:
First, we know that a rectangle consists of 4 edges, that are equal in pairs [e1=e2,e3=e4].
We will show that if there is a polynomial algorithm A to this problem, we can also solve the Partition Problem, by the following algorithm:

input: a group of numbers S=a1,a2,...,an
output: true if and only if the numbers can be partitioned
algorithm:
sum <- a1 + a2 + .. + an
lengths <- a1, a2 , ... , an , (sum*5), (sum*5)
activate A with lengths.
if A answered there is any rectangle [solution is not 0], answer True
else answer False

Correctness:
(1) if there is a partition to S, let it be S1,S2, there is also a rectangle with edges: (sum*5),(sum*5),S1,S2, and the algorithm will yield True.

(2) if the algorithm yields True, there is a rectangle available in lengths, since a1 + a2 + ... + an < sum*5, there are 2 edges with length sum*5, since the 2 other edges must be made using all remaining lengths [as the question specified], each other edge is actually of length (a1 + a2 + ... + an)/2, and thus there is a legal partition to the problem.

Conclusion: There is a reduction PARTITION<=(p) this problem, and thus, this problem is NP-Hard

EDIT:
the backtracking solution is pretty simple, get all possible rectangles, and check each of them to see which is the best.
backtracking solution: pseudo-code:

getAllRectangles(S,e1,e2,e3,e4,sol):
  if S == {}:
     if legalRectangle(e1,e2,e3,e4):
          sol.add((e1,e2,e3,e4))
  else: //S is not empty
     elem <- S[0]
      getAllRectangles(S-elem,e1+elem,e2,e3,e4,sol)
      getAllRectangles(S-elem,e1,e2+elem,e3,e4,sol)
      getAllRectangles(S-elem,e1,e2,e3+elem,e4,sol)
      getAllRectangles(S-elem,e1,e2,e3,e4+elem,sol)

getRectangle(S):
  RECS <- new Set
  getAllRectangles(S,{},{},{},{},RECS)
  getBest(RECS)

EDIT2:
As discussed in the comments, this answer shows not only this is hard to find the BEST rectangle, it is also hard to find ANY rectangle, making this problem hard for heuristic solutions as well.

amit
  • 175,853
  • 27
  • 231
  • 333
  • Basically, in geometric terms, we are trying to build something that is as close to *square* as possible, since square is the biggest possible rectangle of given fixed perimeter. And building a square (or getting as close as possible to a square) involves partitioning our segments into 2 equal-total-length sets (or as close as possible) - horizontals and verticals - which is the Partitioning Problem. – AnT stands with Russia Sep 04 '11 at 07:46
  • @AndreyT: you are right of course, this is the optimization problem for the partition problem, for 4 parts. My answer shows not only finding the BEST rectangle is hard, but also finding if there is ANY rectangle is hard. – amit Sep 04 '11 at 07:49
  • 1
    You are right. This is actually a pair of "nested" Partition Problems over 4 partitions, that requires obtaining perfect equality within each pair of partitions, and on top of that requires minimizing the difference between the pairs. Each requirement is a Partition Problem by itself: one is in "binary" form, another is in "optimization" form. – AnT stands with Russia Sep 04 '11 at 07:56
  • Great answer! Just some things that I'd like to add now: a) Checking all combinations is fine if there are fewsticks. If there are many small sticks perhaps it could be possible to do a dynamic programming solutions? b) Given that this is a programming competition, it is likely that an accepted solution would have to be more refined, perhaps by exploiting simmetries and using a pruning strategy. – hugomg Sep 04 '11 at 14:21
  • @missingno: (a) There might be a dynamic programming solution that does better then brute-force, but it will still be exponential! (i.e. [TSP](http://en.wikipedia.org/wiki/Travelling_salesman_problem): bruteforce O(n!), dynamic programming O((n^2)*(2^n))), so unless P=NP, there is no polynomial solution for this problem at all [including dynamic programming of course]. (b) the OP said there are ~10 numbers as input, 4^10=2^20~=1m, so the backtracking solution should do I guess. – amit Sep 04 '11 at 14:39
  • @amit: I know that. Its just that there is a sort of general tradeof: small N, large sizes -> testing all combinations is faster; large N, small sizes -> dynamic programming over the sizes is faster. – hugomg Sep 04 '11 at 22:50
  • @missingno: I have no idea. The problem as I mentioned is not only finding the *best* rectangle is hard, but finding *any* rectangle is hard. what did you have in mind? I'd love to hear it. – amit Sep 05 '11 at 06:17
  • @amit: I don't have anything in mind, but I do think some sort of dyn-prog solution would pop out if we really needed it. – hugomg Sep 05 '11 at 15:06
2

Here is one solution to the problem in Python. It is not optimized at all. I even check 2, 4 after I check 4,2, for example. But for showing how you can find a solution, I think it is good enough.

def all_but(lst, pos):
    return lst[0:pos]+lst[pos+1:]

def find_sets_with_len(segs, l):
    for i in range(0, len(segs)):
        val = segs[i]
        if (val == l):
            yield [val], all_but(segs, i)
        if (val < l):
            for soln, rest in find_sets_with_len(all_but(segs, i), l - val):
                yield [val]+soln, rest

def find_rect(segs, l1, l2):
    for side1, rest1 in find_sets_with_len(segs, l1):
        for side2, rest2 in find_sets_with_len(rest1, l1):
            for side3, rest3 in find_sets_with_len(rest2, l2):
                return [side1, side2, side3, rest3]

def make_rect(segs):
    tot_len = sum(segs)
    if (tot_len %2) == 0:
        opt_len=tot_len/4
        for l in range(opt_len, 0, -1):
            sides = find_rect(segs, l, tot_len/2-l)
            if sides is not None:
                print(sides)
                return sides
    print("Can't find any solution")

make_rect([4,2,4,4,6,8])

The idea is simple: first, calculate the optimal length (that is, the length to make a square), then search everything starting off with the optimal length, and go down to 1 for one side. For each length, enumerate all sets for one side of the claculated length, then enumerate all sets for the opposite side (of the same length), then if I can find one more set of the remaining length (that is total_len/2 minus the side length I am looking at), then I've got the best solution. This happens in find_rect() function.

vhallac
  • 13,301
  • 3
  • 25
  • 36
0

Well, I get little bit bored so play around with Java to have some experience, can be poorly coded and without tuning, as I am trying to increase my coding skill, comments are welcome. My computer able to answer me for small arrays:)

Output looks like:

Largest rectangle range is ; 48
-------------------------------------------------
input values; [ 4,2,4,4,6,8,9 ]
-------------------------------------------------
Array details of the rectangle:
A1: [ 6 ]
B1: [ 8 ]
A2: [ 2,4 ]
B2: [ 4,4 ]

combination.class using Kenneth algorithm;

import java.math.BigInteger;

public class Combination {

    /**
     * Burak
     */
      private int[] a;
      private int n;
      private int r;
      private BigInteger numLeft;
      private BigInteger total;

    public Combination (int n, int r) {
        if (r > n) {
          throw new IllegalArgumentException ();
        }
        if (n < 1) {
          throw new IllegalArgumentException ();
        }
        this.n = n;
        this.r = r;
        a = new int[r];
        BigInteger nFact = getFactorial (n);
        BigInteger rFact = getFactorial (r);
        BigInteger nminusrFact = getFactorial (n - r);
        total = nFact.divide (rFact.multiply (nminusrFact));
        reset ();
      }

      //------
      // Reset
      //------

      public void reset () {
        for (int i = 0; i < a.length; i++) {
          a[i] = i;
        }
        numLeft = new BigInteger (total.toString ());
      }

      //------------------------------------------------
      // Return number of combinations not yet generated
      //------------------------------------------------

      public BigInteger getNumLeft () {
        return numLeft;
      }

      //-----------------------------
      // Are there more combinations?
      //-----------------------------

      public boolean hasMore () {
        return numLeft.compareTo (BigInteger.ZERO) == 1;
      }

      //------------------------------------
      // Return total number of combinations
      //------------------------------------

      public BigInteger getTotal () {
        return total;
      }

      //------------------
      // Compute factorial
      //------------------

      private static BigInteger getFactorial (int n) {
        BigInteger fact = BigInteger.ONE;
        for (int i = n; i > 1; i--) {
          fact = fact.multiply (new BigInteger (Integer.toString (i)));
        }
        return fact;
      }

      //--------------------------------------------------------
      // Generate next combination (algorithm from Rosen p. 286)
      //--------------------------------------------------------

      public int[] getNext () {

        if (numLeft.equals (total)) {
          numLeft = numLeft.subtract (BigInteger.ONE);
          return a;
        }

        int i = r - 1;
        while (a[i] == n - r + i) {
          i--;
        }
        a[i] = a[i] + 1;
        for (int j = i + 1; j < r; j++) {
          a[j] = a[i] + j - i;
        }

        numLeft = numLeft.subtract (BigInteger.ONE);
        return a;

      }
}

And main Combinator.class;

import java.util.*;

public class Combinator {

/**
 * @param args
 */


private static int[] ad;
private static int[] bd;
private static String a1;
private static String a2;
private static String b1;
private static String b2;
private static int bestTotal =0;


public static void main(String[] args) {
    int[] array={4,2,4,4,6,8,9};
    getBestCombination(array, 1);

    if(bestTotal <= 0){
        System.out.println("System couldnt create any rectangle.");
    }else{
        System.out.println("Largest rectangle range is ; " + bestTotal);
        System.out.println("-------------------------------------------------");
        System.out.println("input values; " + parseArrayToString(array));
        System.out.println("-------------------------------------------------");

        System.out.println("Array details of the rectangle:");
        System.out.println("A1: " + a1);
        System.out.println("B1: " + b1);
        System.out.println("A2: " + a2);
        System.out.println("B2: " + b2);


    }
}



private static void getBestCombination(int[] array, int level){


    int[] a;
    int[] b;

    int bestPerimeter = getTotal(array,true);

    Vector<Vector<Integer>> results = null;

    for(int o=array.length-1;o>=1;o--){
        for(int u=bestPerimeter;u>=1;u--){

            results = Combinator.compute (array, o, u);

            if(results.size() > 0){

                for(int i=0;i<results.size();i++){

                    a = new int[results.elementAt(i).size()];
                    for(int j = 0;j<results.elementAt(i).size();j++){
                        a[j] = results.elementAt(i).elementAt(j);
                    }

                    b = removeItems(array, results.elementAt(i));

                    if(level == 1){
                        getBestCombination(a,2);
                        getBestCombination(b,3);
                    }else if(level == 2){

                        ad = a;
                        bd = b;

                    }else{

                        getBestCombination(a,4);
                        getBestCombination(b,4);

                        if(getTotal(ad, false) == getTotal(a, false) && getTotal(bd, false) == getTotal(b, false)){
                            if(bestTotal<(getTotal(ad, false)*getTotal(bd, false))){
                                bestTotal = getTotal(ad, false)*getTotal(bd, false);
                                a1 = parseArrayToString(ad);
                                a2 = parseArrayToString(a);
                                b1 = parseArrayToString(bd);
                                b2 = parseArrayToString(b);
                            }
                        }else   if(getTotal(ad, false) == getTotal(b, false) && getTotal(bd, false) == getTotal(a, false)){
                            if(bestTotal<(getTotal(ad, false)*getTotal(bd, false))){
                                bestTotal = getTotal(ad, false)*getTotal(bd, false);
                                a1 = parseArrayToString(ad);
                                a2 = parseArrayToString(b);
                                b1 = parseArrayToString(bd);
                                b2 = parseArrayToString(a);
                            }
                        }
                    }
                }
            }
        }
    }
}


private static String parseArrayToString(int[] items){

    String s = "[ ";

    for(int i=0;i<items.length;i++){
        if(i!=items.length-1){

            s = s + items[i] + ",";

        }else{
            s = s + items[i];
        }
    }

    s = s + " ]";

    return s;

}
@SuppressWarnings("rawtypes")
private static int[] removeItems(int[] array, Vector items){


    ArrayList<Integer> res = new ArrayList<Integer>();
    for(int i=0;i<array.length;i++){
        res.add(array[i]);
    }
    for(int u = 0;u<items.size();u++){
        res.remove(items.elementAt(u));
    }
    int[] results = new int[res.size()];
    for(int o=0;o<res.size();o++){
        results[o] = res.get(o);
    }
    return results;
}
private static int getTotal(int[] array,boolean bestPerimeter){
    int sum = 0;

    for (int i = 0; i < array.length; i++) {
      sum += array[i];
    }
   if(bestPerimeter == true){
       if(sum%2!=0){
           sum = sum -1;
       }
       sum = sum/2;
   }
   //System.out.println(sum); 
   return sum;

}

 @SuppressWarnings("rawtypes")
private static int getSum (Vector v) {
        int sum = 0;
        Integer n;
        for (int i = 0; i < v.size (); i++) {
          n = (Integer) v.elementAt(i);
          sum += n.intValue ();
        }
        return sum;
      }



    @SuppressWarnings({ "rawtypes", "unchecked" })
    public static Vector<Vector<Integer>> compute (int[] array, int atATime, int desiredTotal) {
        int[] indices;
        Combination gen = new Combination (array.length, atATime);
        Vector results = new Vector ();
        Vector combination;
        int sum;
        Integer intObj;
        while (gen.hasMore ()) {
          combination = new Vector ();
          indices = gen.getNext ();
          for (int i = 0; i < indices.length; i++) {
            intObj = new Integer (array[indices[i]]);

            combination.addElement (intObj);
          }
          sum = getSum (combination);
          if (sum == desiredTotal) {

            Collections.sort (combination);
            if (!results.contains (combination)) {
              results.addElement (combination);
            }
          }
        }
        return results;
      }

}

HRgiger
  • 2,750
  • 26
  • 37