動態規劃練習題彙總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