问题描述:
某国为了防御敌国的导弹袭击,发展中一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于等于前一发的高度。某天,雷达捕捉到敌国导弹来袭。由于该系统还在试用阶段,所以只用一套系统,因此有可能不能拦截所有的导弹。
输入:
导弹依次飞来的高度 [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)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。