1

动态规划练习题汇总

题目描述
N位同学站成一排,音乐老师要请其中的(N-K)位同学出列,使得剩下的K位同学不交换位置就能排成合唱队形。
合唱队形定义:设K位同学从左到右依次编号为1, 2, …, K,他们的身高分别为T1, T2, …, TK,
则他们的身高满足T1 < T2 < … < Ti, Ti > Ti+1 > … > TK (1 <= i <= K)。
要求:已知所有N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。
输入
同学的身高序列,序列长度为N,如[186,186,150,200,160,130,197,220]
输出
就是最少需要几位同学出列


1 思路
寻找一个同学,其左边同学的身高递增序列+其右边同学的身高递减序列是最长的,该问题难点在于如何计算一个序列的最长递增/递减序列

2 拆分子问题
需要找到以序列中每个元素开始到序列尾的序列的最长递增/递减序列,这些序列中最长的即为整个序列的最长递增/递减序列,子问题就是序列[i:n-1]的最长递增/递减序列,其中 0<=i<n

3 计算
对一个序列[0,k]来说,第i个元素的最长递增序列A(i)=maxLength{ [序列i,shouldAdd(A(j)),其中i<j<k]},当序列i<序列j,shouldAdd(A(j))=A(j),否则shouldAdd(A(j))=null

4 代码
bottom-up DP

const heightArray = [186,186,150,200,160,130,197,220];
class CalHeight {
  constructor(options) {
    this.heightArray = Array.isArray(options) ? options : [];
  }
  getScoreMemorize() {
    let maxArr = [];
    for (let i = 0; i < this.heightArray.length; i++) {
      const left = this.getMaxAscend(this.heightArray.slice(0, i + 1));
      const right = this.getMaxAscend(this.heightArray.slice(i, this.heightArray.length).reverse()).reverse();
      const newArr = [...new Set(left.concat(right))];
      if (newArr.length > maxArr.length) {
        maxArr = newArr;
      }
    }
    console.log(`最少需要 ${this.heightArray.length - maxArr.length} 位同学出列,留在队里的同学的身高是${maxArr.join()}`);
  }
  getMaxAscend(arr){
    let ascendArr = [];
    for (let i = arr.length - 1; i >= 0; i--){
      let maxArr = [];
      ascendArr[i] = {
        id: i,
        value: arr[i],
        arr: [arr[i]]
      };
      for (let j = i + 1; j < arr.length; j++){
        if (arr[i] < ascendArr[j].value) {
          if (ascendArr[j].arr.length > maxArr.length) {
            maxArr = ascendArr[j].arr;
          }
        }
      }
      ascendArr[i].arr = ascendArr[i].arr.concat(maxArr);
    }
    let result = [];
    ascendArr.forEach(item => {
      if (item.arr.length > result.length) {
        result = item.arr;
      }
    });
    return result;
  }
}
new CalHeight(heightArray).getScoreMemorize();

recursive DP

const heightArray = [186,186,150,200,160,130,197,220];
class CalHeight {
  constructor(options) {
    this.heightArray = Array.isArray(options) ? options : [];
  }
  getScoreRecursive() {
    let maxArr = [];
    for (let i = 0; i < this.heightArray.length; i++) {
      const left = this.getAscend(this.heightArray.slice(0, i + 1));
      const right = this.getAscend(this.heightArray.slice(i, this.heightArray.length).reverse()).reverse();
      const newArr = [...new Set(left.concat(right))];
      if (newArr.length > maxArr.length) {
        maxArr = newArr;
      }
    }
    console.log(`最少需要 ${this.heightArray.length - maxArr.length} 位同学出列,留在队里的同学的身高是${maxArr.join()}`);
  }
  getAscend(arr) {
    let max = [];
    let memo = {};
    this.getAscendRecursive(arr,0,memo);
    Object.keys(memo).forEach(item => {
      if (memo[item].arr.length > max.length) {
        max = memo[item].arr;
      }
    });
    return max;
  }
  getAscendRecursive(arr, n = 0, memo = {}) {
    if (memo[n]) {
      return memo[n];
    }
    if (n === arr.length - 1) {
      memo[n] = { value: arr[n], arr: [arr[n]]};
      return memo[n];
    } else {
      let max = [arr[n]];
      for (let i = n + 1; i < arr.length; i++){
        const next = this.getAscendRecursive(arr, i,memo);
        if (arr[n] < next.value) {
          if (next.arr.length >= max.length) {
            max = [].concat(arr[n], next.arr);
          }
        }
      }
      memo[n] = { value: arr[n],arr: max };
      return memo[n];
    }
  }
}
new CalHeight(heightArray).getScoreRecursive();

5 时间复杂度
对每个同学,都需要计算其左边同学的身高递增序列+其右边同学的身高递减序列;计算一个序列[0:k]的最长递增序列,需要计算序列中每个元素的最长递增序列,且计算第i个元素的最长递增序列时,需要进行k-i次比较,因此时间复杂度为O(n3)


小利子
118 声望6 粉丝