[Algorithm] Meeting hour optimization (Kanpsack problem) and Dynamic programming

For example we have array of meeting objects:

const data = [
  { name: "m1", hours: 2 },
  { name: "m2", hours: 4 },
  { name: "m3", hours: 3 },
  { name: "m4", hours: 3 },
  { name: "m5", hours: 1 }
];

 

For a day, 8 hours, we want to take as any meetings as possible:

const res = optimizeMeetings(data, 8);

 

You should write function 'optimizeMeetings', get the results of selected meetings to attend.

 

This problem is the same as Knapack problem, we can construct a table:

hours / total 1 2 3 4 5 6 7 8
2  0  2  2  2  2  2  2
4  0  2  2  4  4  6  6  6
3  0  2  3  4  5  6  7 7
3  0  2  3  4  5  6  7  8
1  1  2  3  4  5  6  7 8

The max hours we can take is the last row & col value, which in the end should be 8.

 

Then we should trace back the table to find which items should be included.

 

 

Final Code:

/**@description
 * When we have our result for Knapsack problem, we want to back trace to get the selected items.
 *
 * What we need to do is trace form last item of the memo, moving up
 */
const backTrace = (hours, totalHours, memo) => {
  function helper(memo, row, col) {
    let current = memo[row][col];
    let selected = [];
    while (current >= 0 && row >= 0 && col >= 0) {
      // If we reach the first row, then check whether we have the remaining?
      // If yes then we need to add this row item into final result
      if (row === 0 && current !== 0) {
        selected.push(row);
        break;
      }

      let sameRowPrevCol = memo[row][col - 1];
      let prevRowSameCol = memo[row - 1][col];

      if (current !== sameRowPrevCol && prevRowSameCol !== current) {
        // Item should be selected if the value with sibling values are differnet
        selected.push(row);
        // calcuate the remaining
        col = current - hours[row] - 1;
        row = row - 1;
      } else if (prevRowSameCol === current && current !== sameRowPrevCol) {
        // current is coming from previous row with the same column, reset row
        row = row - 1;
      } else if (current === sameRowPrevCol && prevRowSameCol !== current) {
        // current is coming from previous column with the same row, reset column
        col = col - 1;
      }
      // Update current with new row and new column
      current = memo[row][col];
    }
    return selected;
  }

  return helper(memo, hours.length - 1, totalHours.length - 1);
};

const getMaxHours = (hours, totalHours) => {
  let memo = [...new Array(hours.length)].map(
    x => new Array(totalHours.length)
  );
  function helper(hours, totalHours, memo) {
    for (let row in hours) {
      const value = hours[row];
      for (let col in totalHours) {
        // Fill in the first row
        if (!memo[row - 1]) {
          memo[row][col] = value <= totalHours[col] ? value : 0;
          continue;
        }

        // if the current value is larger than constrain, we use previous value
        const prevRowSameCol = memo[row - 1][col];
        if (value > totalHours[col]) {
          memo[row][col] = prevRowSameCol;
          continue;
        }

        // if the current value is equal to constrain, then Max{value, prevRowSameCol}
        if (value === totalHours[col]) {
          memo[row][col] = Math.max(value, prevRowSameCol);
        }

        // if the current value is smaller than constrain
        // Math {value + memo[row - 1][diff]: where diff is constrain-value, prevRowSameCol}
        if (value < totalHours[col]) {
          const diff = totalHours[col] - value - 1;
          memo[row][col] = Math.max(
            prevRowSameCol,
            value + memo[row - 1][diff]
          );
        }
      }
    }
    return memo;
  }
  memo = helper(hours, totalHours, memo);
  const selectedIndex = backTrace(hours, totalHours, memo);

  return {
    memo,
    selectedIndex
  };
};

function* genearteNumberAry(start, num) {
  let i = start;
  while (i <= num) {
    yield i;
    i++;
  }
}

/**
 * Main
 */
/**
 * @param meetings: [{name: string, hours: number}]
 * @param haveHours: number
 *
 * @returns [meetings]
 */
function optimizeMeetings(meetings, haveHours) {
  const hours = meetings.map(m => m.hours);
  const haveHoursAry = Array.from(genearteNumberAry(1, haveHours));
  const { selectedIndex } = getMaxHours(hours, haveHoursAry);
  return selectedIndex.map(i => meetings[i]);
}

const data = [
  { name: "m1", hours: 2 },
  { name: "m2", hours: 4 },
  { name: "m3", hours: 3 },
  { name: "m4", hours: 3 },
  { name: "m5", hours: 1 }
];

const res = optimizeMeetings(data, 8);
console.log(JSON.stringify(res)); // [{"name":"m4","hours":3},{"name":"m3","hours":3},{"name":"m1","hours":2}]

 

posted @ 2019-03-15 21:35  Zhentiw  阅读(1370)  评论(0编辑  收藏  举报