基本动态规划模型
1、数字金字塔:观察下方的数字金字塔。写一个程序查找从最高点到底部任意处结束的路径,使路径经过数字的和最大。每一步可以从当前点走到左下方的点也可以到达右下方的点。
------------------------ 13
------------------- 11 ------ 8
-------------- 12 ------ 7 ----- 26
----------- 6 ---- 14 ----- 15 ----- 8
-------12 ---- 7 ----- 13 ----- 24 ---- 11
输出:86
解法1:搜索
let arr = [[13],[11, 8],[12, 7, 26],[6, 14, 15, 8],[12, 7, 13, 24, 11]], level = arr.length function search(x, y) { if (x === level-1) return arr[x][y] return arr[x][y] + Math.max(search(x+1, y), search(x+1, y+1)) } search(0, 0)
这可能是最差的方法,像search(1, 0)
调用了search(2, 1)
,search(1, 1)
又调用了arr[2][1]
,这种方法存在许多重复计算。
解法2:记忆化搜索
受第一种方法启发,用一个数组存储每个节点的结果。
let arrs = [[13],[11, 8],[12, 7, 26],[6, 14, 15, 8],[12, 7, 13, 24, 11]], results = [] Array.prototype.initialize = function(sampleArray){ //在Array原型中添加初始化空数组的方法 for (let element of sampleArray) { let temp = [] for (let num of element) { temp.push(0) } this.push(temp) } } function maxSum(targetArray, map) { let level = targetArray.length, ans function search(x, y) { if (!map[x][y]) { //map[x][y]为0 if (x === level-1) map[x][y] = targetArray[x][y] //结果数组最后一行的元素和原数组的一样 else map[x][y] = targetArray[x][y] + Math.max(search(x+1,y), search(x+1,y+1)) } return map[x][y] } return ans = search(0, 0) } results.initialize(arrs) //结果数组results初始化,大小和arrs一样,只是数字元素都是0 maxSum(arrs, results) //求最长路径
稍微用到点JS的语言特性,不过应该不难理解。
解法3:动态规划(顺推)
let arrs = [[13],[11, 8],[12, 7, 26],[6, 14, 15, 8],[12, 7, 13, 24, 11]], max for (var i = 1, level = arrs.length; i < level; i++) { //从第二行开始 for (var j = 0; j <= i; j++) { arrs[i][j] += Math.max(arrs[i-1][j-1] || 0, arrs[i-1][j] || 0) //边上的元素可能没有左上或右上元素,这时设缺少的元素为0 } } Math.max(...arrs[level-1])
注意,解法3和解法4都直接用原数组存储计算结果
解法4:动态规划(逆推)
let arrs = [[13],[11, 8],[12, 7, 26],[6, 14, 15, 8],[12, 7, 13, 24, 11]], level = arrs.length, max for (let i = level - 2; i >= 0; i--) { for (let j = 0; j <= i; j++) { arrs[i][j] += Math.max(arrs[i+1][j], arrs[i+1][j+1]) } } arrs[0][0]
2、求最长不下降序列:设有由n个不同的整数组成的数列b(1),b(2),···,b(n)。若存在 i1 < i2 < i3 < ··· < ie 且有 b(i1) < b(i2) < b(i3) < ··· <b(ie),则称为长度为e的不下降序列。求出最长的不下降序列。
输入样例:13,7,9,16,38,24,37,18,44,19,21,22,63,15
输出样例:7,9,16,18,19,21,22,63
思路:从末尾元素开始逆推,15的最长子串是“15”,所以其最长子串长度为1;下个元素63,和15比较,不构成不下降序列,则63的最长子串为“63”,长度为1;下个元素22,依次和63、15比较,发现和63构成不下降序列,于是长度可从63的最长子串长度+1获得,再记录下63在数列中的位置;接着下个元素21...
let arr = [13, 7, 9, 16, 38, 24, 37, 18, 44, 19, 21, 22, 63, 15], len = arr.length, lengths = new Array(len).fill(1), //长度同arr,元素均为1的数组,用于存放arr中每个元素对应的最长子串长度 nexts = new Array(len).fill(-1), //长度同arr,元素均为-1的数组,用于存放arr中每个元素的后继元素的坐标 ans = [], //存放最长子串 index //用于遍历最长子串 for (let i = len-2; i >= 0; i--) { //从倒数第二个数开始计算 for (let j = i+1; j < len; j++) { //逐一和其后的元素进行比较 if (arr[i] < arr[j] && lengths[i] < lengths[j]+1) { lengths[i] = lengths[j]+1 nexts[i] = j } } } index = lengths.indexOf(Math.max(...lengths)) while (index !== -1) { //尽量不要使用while(index),万一index等于0退出循环呢? ans.push(arr[index]) index = nexts[index] } console.log(ans)
3、拦截导弹:某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种系统有一个缺陷:虽然它的第一次拦截能够到达任意的高度,但以后每一次都不能高于前一次拦截导弹的高度。某天,雷达捕捉到敌国的导弹来袭。因为只有一套系统,所以有可能无法拦截所有的导弹。输入导弹依次飞来的高度,计算这套系统最多能拦截多少导弹,如果要拦截所有导弹至少需要配备多少套这种导弹拦截系统。
输入样例:389,207,155,300,299,170,158,65
输出样例:6,2
第一问的实现和上题大体相同,只有少许修改,已注明
let arr = [389,207,155,300,299,170,158,65], len = arr.length, lengths = new Array(len).fill(1), nexts = new Array(len).fill(-1), ans = [] index for (let i = len-2; i >= 0; i--) { for (let j = i+1; j < len; j++) { if (arr[i] >= arr[j] && lengths[i] < lengths[j]+1) { //修改了比较条件 lengths[i] = lengths[j]+1 nexts[i] = j } } } index = lengths.indexOf(Math.max(...lengths)) while (index !== -1) { ans.push(arr[index]) index = nexts[index] } console.log(ans.length) //数组长度即所求
第二问采用贪心算法解决。导弹依次飞来,对于每颗导弹,使用能拦截且上次拦截高度最低的系统来拦截。若现有系统都无法拦截,则新增一套系统。代码省略。
4、最短路径:有A、B1-3、C1-3、D1-2、E共10座城市,由A向E单向通行(亦无法通向自己)。用矩阵M表示它们之间的路径长度,第i行第j个元素记为M[i][j]。例如M[1]代表城市B1到各城市的路径长度,M[1][5]代表城市B到城市C2的路径长度,为14(0代表城市之间没有通路)。试用动态规划的最优化原理求出A->E的最短路径及其长度。
00 02 05 01 00 00 00 00 00 00
00 00 00 00 12 14 00 00 00 00
00 00 00 00 06 10 04 00 00 00
00 00 00 00 13 12 11 00 00 00
00 00 00 00 00 00 00 03 09 00
00 00 00 00 00 00 00 06 05 00
00 00 00 00 00 00 00 00 10 00
00 00 00 00 00 00 00 00 00 05
00 00 00 00 00 00 00 00 00 02
00 00 00 00 00 00 00 00 00 00
思路:用Distance(X->Y)
代表城市X到城市Y的最短距离Distance(A -> E) = Min(Distance(B1 -> E), Distance(B2 -> E), Distance(B3 -> E))
Distance(B1 -> E) = Min(Distance(C1 -> E), Distance(C2 -> E)
...
实现:首先处理输入,将字符串转换为符合要求的数组
//跨行字符串 var str = `00 02 05 01 00 00 00 00 00 00 00 00 00 00 12 14 00 00 00 00 00 00 00 00 06 10 04 00 00 00 00 00 00 00 13 12 11 00 00 00 00 00 00 00 00 00 00 03 09 00 00 00 00 00 00 00 00 06 05 00 00 00 00 00 00 00 00 00 10 00 00 00 00 00 00 00 00 00 00 05 00 00 00 00 00 00 00 00 00 02 00 00 00 00 00 00 00 00 00 00` var arr = [] str.split(/\s/) //切割字符串,得到字符串数组 .forEach(function(el, index){ //index为当前遍历元素的下标 if (index % 10 === 0) this.push([]) //到达行首元素,则新建一个子数组 this[this.length-1].push(Number(el)) //给arr当前的末尾子数组中推入数字 }, arr) //设置this为arr
得到可用的数组arr,开始解题
let distances = [], //长度为10的数组,用于存储10座城市到E城的最短距离,E城到E城距离默认为0 route = [], cities = ['A', 'B1', 'B2', 'B3', 'C1', 'C2', 'C3', 'D1', 'D2', 'E'] function printRoute(arr) { //打印最短路线 let i = 0, route = 'A' while (arr[i]) { i = arr[i] route += ' -> ' + cities[i] } return route } distances[arr.length-1] = 0 route[arr.length-1] = 0 for (let len = arr.length, i = len-2; i >= 0; i--) { //i代表当前正在计算cities[i]到E城的最短路径 distances[i] = Number.POSITIVE_INFINITY for (let j = i+1, len = arr[i].length; j < len; j++) { if (!arr[i][j]) continue //跳过0元素 temp = arr[i][j] + distances[j] if (temp < distances[i]) { distances[i] = temp route[i] = j //记录最短路线中下一个城市在cities数组中的下标 } } } console.log(distances[0]) printRoute(route)