Data Structures, Algorithms, & Applications in Java
Chapter 54, Exercise 4

To get the candidate heights, we first compute all hsum(i,k) values. Then, these values are sorted using the merge sort method. Following the sort, duplicate values are eliminated by making a left to right sweep over the sorted candidate heights. Next, we make a binary search for the minimum height for which a folding into a rectangle of given width is possible. If no folding is possible for a trial candidate height tHeight, then candidate heights smaller than tHeight are eliminated because no stack folding for these smaller heights is possible either. When a folding is possible for a trial candidate height tHeight, then candidate heights larger than tHeight are eliminated because these larger heights cannot result in the minimum height folding.

The code is given below.
public class VariableWidthFolding2
{
   // data members
   static int n;                 // number of components;
   static int widthSum;          // sum of widths of all components;
   static int [] h;              // component heights
   static int [] w;              // component widths
   static int [] r;              // space to be left at column ends
   static int [][] hsum;         // hsum(i,k)
   static int [] theW;           // W_i for current height choice
   static int [] kay;            // kay values for current height choice;
   
   /** fold components with heights h[1 .. h.length - 1] and widths
     * w[1 .. w.length - 1] into a rectangle of width theWidth and
     * minimum height
     * @param r is array of space to be left at column ends
     *        length of r must exceed that of h
     * @param theW[i] is W_i
     * @param hsum[i][k] is hsum(i,k)
     * values of bestW[1..h.length] and bestKay[1..h.length-1] are computed
     * by this method
     * @return minimum folding height
     * @return 0 if folding into a theWidth rectangle is not possible */
   public static int minimumHeightFolding(int [] h, int [] r, int [] w,
                         int theWidth, int [] bestW, int [] bestKay)
   {    
      // set class data members
      VariableWidthFolding2.n = h.length - 1;
      VariableWidthFolding2.h = h;
      VariableWidthFolding2.w = w;
      VariableWidthFolding2.r = r;
      VariableWidthFolding2.theW = new int [n + 2];
      theW[n + 1] = 0;
      VariableWidthFolding2.kay = new int [n + 1];
      // determine sum of widths
      widthSum = 0;
      for (int i = 1; i <= n; i++)
         widthSum += w[i];
      // determine hsum(i,k)
      hsum = new int [n + 1][n + 1];
      for (int i = 1; i <= n; i++)
      {
         hsum[i][i] = h[i];
         for (int k = i + 1; k <= n; k++)
            hsum[i][k] = hsum[i][k - 1] + h[k];
      }
      r[0] = r[n + 1] = 0;

      // collect possible rectangle height values into a 1D array
      Integer [] height = new Integer [n * n];
      int k = 0;      // cursor for height[]
      for (int i = 1; i <= n; i++)
         for (int j = 1; j <= n; j++)
            height[k++] = new Integer(hsum[i][j] + r[i] + r[j + 1]);

      // sort the possible heights
      MergeSort.mergeSort(height);

      // eliminate duplicates
      k = 0;
      for (int i = 1; i < n * n; i++)
         if (height[k].compareTo(height[i]) < 0)
            height[++k] = height[i];

      // do a binary search over the k + 1 distinct heights
      int minHeight = 0;
      int left = 0;
      int right = k;
      while (left <= right)
      {
         int middle = (left + right)/2;
         variableWidthFolding(height[middle].intValue());
         if (theW[1] <= theWidth)
         {// height[middle] is feasible
            minHeight = height[middle].intValue();
            // save theW and kay
            for (int i = 1; i <= n; i++)
            {
               bestW[i] = theW[i];
               bestKay[i] = kay[i];
            }
            right = middle - 1;  // do not examine larger heights
         }   
         else
            // height[middle] is infeasible, do not examine smaller heights
            left = middle + 1;
      }
      return minHeight;
   }    

   /** fold components into a minimum width rectangle of height theHeight
     * values of theW[1..h.length] and kay[1..h.length-1] are computed
     * by this method */
   public static void variableWidthFolding(int theHeight)
   {
      for (int i = n; i > 0; i--)
      {// compute theW[i] using Eq. 54.5
         int wmax = 0,                   // wmax(i,k)
             minW = widthSum + 1;        // min value for W_i so far
   
         for (int k = i; k <= n; k++)
         {
            if (hsum[i][k] > theHeight)
               // infeasible
               break;
            if (w[k] > wmax)
               wmax = w[k]; 
            if (hsum[i][k] + r[i] + r[k + 1] <= theHeight &&
                wmax + theW[k + 1] < minW)
            {
               minW = wmax + theW[k + 1];
               kay[i] = k;
            }
         }
         theW[i] = minW;
      }
      return;
   }
   
   /** output fold points */
   public static void traceback(int [] kay)
   {
      if (kay[1] >= n)
         System.out.println("There are no fold points");
      else
      {// there is at least one fold point
         int i = 1;
         System.out.print("The fold points are ");
         while (kay[i] < n)
         {
            i = kay[i] + 1;
            System.out.print(i + " ");
         }
         System.out.println();
      }
   }
}