动态规划练习题汇总

问题描述:

clipboard.png

在上面的数字三角形中寻找一条从顶部到底边的路径,使得路径上所经过的数字之和最大。
路径上的每一步都只能往左下或 右下走。只需要求出这个最大和即可,不必给出具体路径。 三角形的行数大于1小于等于100,数字为 0 - 99

输入:
三角形序列:[[7],[3,8],[8,1,0],[2,7,4,4],[4,5,2,6,5]]

输出:
最大和,走过的数字序列

1 思路
把三角形看做一个二叉树,根节点为第一层,往第i+1层走时,是在第i层的基础上增加i+1个可选项,用到达第i层的路径和加上可选项,将有最大和的可选项纳入路径

2 拆分子问题
第i层的每个节点都有其最大路径和,往第i+1层走时,每个节点都有两个选项,因此可以计算得到第i+1层每个节点的最大路径和

3 计算
以w(i)(k)表示第i层的第k个节点的值,以S(i+1)作为到达第i+1层的最大路径和,以Sk(i+1)作为第i+1层第k个节点的最大路径和,其中1<=k<=i+1,S(i+1)=max{ Sk(i) + max{w(i+1)(k),w(i+1)(k+1)}, 其中1<=k<=i}

4 代码
bottom-up DP

const numbers = [[7], [3, 8], [8, 1, 0], [2, 7, 4, 4], [4, 5, 2, 6, 5]];
class CalTree {
  constructor(numbers) {
    this.numbers = numbers;
    this.sums = numbers.map((item,rowIndex) => {
      return item.map((item, index) => {
        return rowIndex === 0 && index === 0 ? item : 0;
      });
    });
    this.path = numbers.map(item => {
      return item.map(item => item);
    });
  }
  getSum() {
    for (let i = 0;i<this.numbers.length-1; i++){
      let nextSums = this.sums[i+1];
      let curSums = this.sums[i];
      let nextItems = this.numbers[i + 1];
      for (let index = 0; index < curSums.length; index++){
        const item = curSums[index];
        const temp1 = item + nextItems[index];
        if (temp1 > nextSums[index]) {
          nextSums[index] = temp1;
          this.path[i+1][index] = [].concat(this.path[i][index], nextItems[index]);
        }
        const temp2 = item + nextItems[index + 1];
        if (temp2 > nextSums[index + 1]) {
          nextSums[index + 1] = temp2;
          this.path[i+1][index + 1] = [].concat(this.path[i][index], nextItems[index + 1]);
        }
      }
    }
    const lastSumArr = this.sums[this.sums.length - 1];
    const lastPathArr = this.path[this.sums.length - 1];
    let maxSum = 0, maxSumPath = [];
    lastSumArr.forEach((item,index) => {
      if (item > maxSum) {
        maxSum = item;
        maxSumPath = lastPathArr[index];
      }
    });
    console.log("最大和是:", maxSum, ", 路径为:", maxSumPath.join(","));
  }
}
new CalTree(numbers).getSum();

recurssive DP

const numbers = [[7], [3, 8], [8, 1, 0], [2, 7, 4, 4], [4, 5, 2, 6, 5]];
class CalTree {
  constructor(numbers) {
    this.numbers = numbers;
    this.sums = numbers.map((item,rowIndex) => {
      return item.map((item, index) => {
        return rowIndex === 0 && index === 0 ? item : 0;
      });
    });
    this.path = [].concat(numbers);
  }
  getSumRecursive() {
    let lastArr = this.sums[this.sums.length - 1];
    for (let i = 0; i < lastArr.length; i++){
      this.cal(this.sums.length - 1, i);
    }
    const lastSumArr = this.sums[this.sums.length - 1];
    const lastPathArr = this.path[this.sums.length - 1];
    let maxSum = 0, maxSumPath = [];
    lastSumArr.forEach((item, index) => {
      if (item > maxSum) {
        maxSum = item;
        maxSumPath = lastPathArr[index];
      }
    });
    console.log("最大和是:", maxSum, ", 路径为:", maxSumPath.reverse().join(","));
  }
  cal(depth, index) {
    if (this.sums[depth][index]) {
      return this.sums[depth][index];
    }
    const curItem = this.numbers[depth][index];
    const upSums = this.sums[depth - 1];
    const temp = [].concat(this.path[depth][index]);
    if (index > 0) {
      const temp2 = this.cal(depth - 1, index - 1) + curItem;
      if (temp2 > this.sums[depth][index]) {
        this.sums[depth][index] = temp2;
        this.path[depth][index] = [].concat(temp, this.numbers[depth - 1][index - 1]);
      }
    }
    if (index < upSums.length) {
      const temp1 = this.cal(depth - 1, index) + curItem;
      if (temp1 > this.sums[depth][index]) {
        this.sums[depth][index] = temp1;
        this.path[depth][index] = [].concat(temp, this.numbers[depth - 1][index]);
      }
    }
    return this.sums[depth][index];
  }

}
new CalTree(numbers).getSumRecursive();

5 时间复杂度
令三角形的数字个数为n,可以发现,除了最底行,其他行的每个数字仅参与两次计算,故时间复杂度为O(n)


小利子
118 声望6 粉丝