2

动态规划练习题汇总

问题描述:
某国为了防御敌国的导弹袭击,发展中一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于等于前一发的高度。某天,雷达捕捉到敌国导弹来袭。由于该系统还在试用阶段,所以只用一套系统,因此有可能不能拦截所有的导弹。
输入:
导弹依次飞来的高度 [h1, h2, h3,...hn]
输出:
最多能拦截的导弹数目


思路:
导弹飞来,我们可以选择拦截或者不拦截,但拦截第一次后,后面的拦截高度受限。一次拦截结果是成功/失败,我们需要将拦截成功次数score最大化。

1 拆分子问题
一次拦截结果是成功或者失败,score可加1或保持不变。对第i个导弹,拦截它的次序可能是第1次,第2次。。。第i次,所以我们需要计算的是第1次,第2次。。。第i次拦截的成功次数

2 得分计算
第i次拦截的成功次数为 S(i) = max(S(j)+shootScore(i,j+1)) ,其中,1<=j<i,shootScore(i,j+1)表示当第i个导弹飞来,我们已经进行了j次拦截,第j+1次拦截的成功/失败,成功为1,失败为0,初始值S(1)=1

3 代码
bottom-up algorithm
该解法的问题在于:拆分出的子问题个数是n,第i个子问题需要考虑i-1种情况,计算shootScore(i,j+1)需要进行i-j-1次计算,所以时间复杂度为O(n3)

const missileArray = [389, 207, 155, 300, 299, 170, 158, 65];
class Calmissile {
  constructor(options) {
    this.score = [];
    this.missileArray = Array.isArray(options) ? options : [];
  }
  shootScore(cur, len) {
    let maxHeight = this.missileArray[cur-len];
    for (let i = cur - len; i < cur; i++){
      if (this.missileArray[i] < maxHeight) {
        maxHeight = this.missileArray[i]
      }
    }
    return this.missileArray[cur] <= maxHeight ? 1 : 0;
  }
  getScore() {
    for (let i = 0; i < this.missileArray.length; i++){
      for (let j = 0; j < i; j++){
        this.score[j] += this.shootScore(i,j+1);
      }
      this.score.unshift(1);
    }
    let result = [];
    const max = Math.max(...([].concat(this.score)));
    this.score.forEach((item, index) => {
      if (item === max) {
        result.push([index,item]);
      }
    });
    result.forEach(item => {
      console.log(`从第${item[0]}枚导弹开始拦截,可以拦下${item[1]}枚导弹`);
    })
  }
}
new Calmissile(missileArray).getScore();

bottom-up DP+ memorize
不难发现时间复杂度为O(n3)的解法中,shootScore有大量的重复计算,因此我们可以缓存score和maxHeight,减少计算量

const missileArray = [389, 207, 155, 300, 299, 170, 158, 65];
class Calmissile {
  constructor(options) {
    this.maxScore = 1;
    this.missileArray = Array.isArray(options) ? options : [];
  }
  getScoreMemorize() {
    let memorize = [{ id: 0, score: 1, maxHeight: this.missileArray[0]}];
    for (let i = 1; i < this.missileArray.length; i++) {
      for (let j = 0; j < i; j++) {
        if (this.missileArray[i] <= memorize[j].maxHeight) {
          if (memorize[j].score + 1 >= this.maxScore) {
            this.maxScore = memorize[j].score + 1;
          }
          memorize[j] = {
            id: j+1,
            score: memorize[j].score+1,
            maxHeight: this.missileArray[i]
          }
        } else {
          if (memorize[j].score >= this.maxScore) {
            this.maxScore = memorize[j].score;
          }
          memorize[j] = {
            id: j+1,
            score: memorize[j].score,
            maxHeight: memorize[j].maxHeight
          }
        }
      }
      memorize.unshift({ id: 0, score: 1, maxHeight: this.missileArray[i] });
    }
    let logs = [];
    memorize.forEach((item, index) => {
      if (item.score === this.maxScore) {
        logs.push([index, item.score]);
      }
    });
    logs.forEach(item => {
      console.log(`从第${item[0]}枚导弹开始拦截,可以拦下${item[1]}枚导弹`);
    })
  }
  
}

new Calmissile(missileArray).getScoreMemorize();

recurssive DP
与bottom-up+memorize的思路一致,不过采用递归的写法

const missileArray = [389, 207, 155, 300, 299, 170, 158, 65];
class Calmissile {
  constructor(options) {
    this.maxScore = 1;
    this.missileArray = Array.isArray(options) ? options : [];
  }
  getScoreRecurssive() {
    let logs = [];
    const result = this.getScoreV2(this.missileArray.length - 1);
    result.forEach((item, index) => {
      if (item.score === this.maxScore) {
        logs.push([index, item.score]);
      }
    });
    logs.forEach(item => {
      console.log(`从第${item[0]}枚导弹开始拦截,可以拦下${item[1]}枚导弹`);
    })
  }
  getScoreV2(n) {
    if (n === 0) {
      return [{
        id: 0,
        score: 1,
        maxHeight: this.missileArray[n]
      }]
    } else {
      let newArr = this.getScoreV2(n - 1).map((item,index) => {
        if (this.missileArray[n] <= item.maxHeight) {
          if (item.score+1 >= this.maxScore) {
            this.maxScore = item.score+1;
          }
          return {
            id: index+1,
            score: ++item.score,
            maxHeight: this.missileArray[n]
          }
        } else {
          if (item.score >= this.maxScore) {
            this.maxScore = item.score;
          }
          return {
            id: index+1,
            score: item.score,
            maxHeight: item.maxHeight
          }
        }
      });
      newArr.unshift({
        id: 0,
        score: 1,
        maxHeight: this.missileArray[n]
      });
      return newArr;
    }
  }
}
new Calmissile(missileArray).getScoreRecurssive();

4 时间复杂度
拆分出的子问题个数是n,第i个子问题需要计算i次新的score和maxHeight,故时间复杂度为O(n2)


小利子
118 声望6 粉丝