动态规划练习题汇总segmentfault
问题描述:
某国为了防护敌国的导弹袭击,发展中一种导弹拦截系统。可是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹可以到达任意的高度,可是之后每一发炮弹都不能高于等于前一发的高度。某天,雷达捕捉到敌国导弹来袭。因为该系统还在试用阶段,因此只用一套系统,所以有可能不能拦截全部的导弹。
输入:
导弹依次飞来的高度 [h1, h2, h3,...hn]
输出:
最多能拦截的导弹数目缓存
思路:
导弹飞来,咱们能够选择拦截或者不拦截,但拦截第一次后,后面的拦截高度受限。一次拦截结果是成功/失败,咱们须要将拦截成功次数score最大化。this
1 拆分子问题
一次拦截结果是成功或者失败,score可加1或保持不变。对第i个导弹,拦截它的次序多是第1次,第2次。。。第i次,因此咱们须要计算的是第1次,第2次。。。第i次拦截的成功次数code
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)get
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,减小计算量it
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的思路一致,不过采用递归的写法io
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)console