动态规划常被人比作是递归的逆过程,而贪心算法在很多求优问题上(也许不是最优解),是不二之选
动态规划
动态规划:
- 从底部开始解决问题,将所有小问题解决掉,然后合并成一个整体的解决方案。
- 使用一个数组建立一张表,用于存放被分解成众多子问题的解。 递归:是从顶部开始将问题分解,通过解决掉所有分解出小问题的方式,来解决整个问题。
使用递归去解决问题虽然简洁,但效率不高。包括 JavaScript 在内的众多语言,不能高效地将递归代码解释为机器代码,尽管写出来的程序简洁,但是执行效率低下。但这并不是说使用递归是件坏事,本质上说,只是那些指令式编程语言和面向对象的编程语言对递归 的实现不够完善,因为它们没有将递归作为高级编程的特性。
斐波拉契数列
递归解决:
function fibo (n) {
if (n <= 0) return 0;
if (n === 1) return 1;
return fibo(n - 1) + fibo(n - 2);
}
动态规划解决
使用动态规划解决最简单的子问题着手的话,效果就很不一样了。存取每一次产生子问题的结果a,b
function fibo (n) {
if (n <= 0) return 0;
if (n <= 1) return 1;
var a = 0, b = 1;
for (var i = 2; i <= n; i++) {
b = a + b;
a = b - a;
}
return b;
}
寻找最长公共子串
比如 abcdefg 和 emefabcde
暴力循环解决
function maxSubString (str1, str2) {
if (!str1 || !str2) return '';
var len1 = str1.length,
len2 = str2.length;
var maxSubStr = '';
for (var i = 0; i < len1; i++) {
for (var j = 0; j < len2; j++) {
var tempStr = '',
k = 0;
while ((i + k < len1) && (j + k < len2) && (str1[i + k] === str2[j + k])) {
tempStr += str1[i + k];
k++;
}
if (tempStr.length > maxSubStr.length) {
maxSubStr = tempStr;
}
}
}
return maxSubStr;
}
动态规划
function maxSubString(str1, str2){
var maxLen = 0;
var index = 0;
var arr = new Array();
for (var i = 0; i <= str1.length + 1; i++) {
arr[i] = new Array();
for (var j = 0; j <= str2.length + 1; j++) {
arr[i][j] = 0;
}
}
for(var i = 0; i <= str1.length; i++){
for(var j = 0; j <= str2.length; j++){
if(i == 0 || j == 0){
arr[i][j] = 0
}else{
if (str1[i] == str2[j] && str1[i - 1] == str2[j - 1]) {
arr[i][j] = arr[i - 1][j - 1] + 1;
}else{
arr[i][j] = 0;
}
}
if(arr[i][j] > maxLen){
maxLen = arr[i][j];
index = i;
}
}
}
var str = "";
if(maxLen == 0){
return "";
}else{
return str1.slice(index,maxLen);
}
}
maxSubString(str1, str2) // abcde
背包问题
递归解决
function knapsack (capacity, objectArr, order) {
if (order < 0 || capacity <= 0) {
return 0;
}
if (arr[order].size > capacity) {
return knapsack(capacity, objectArr, order - 1);
}
return Math.max(
arr[order].value + knapsack(capacity - arr[order].size, objectArr, order - 1),
knapsack(capacity, objectArr, order - 1)
);
}
console.log(knapsack(16, [
{value: 4, size: 3},
{value: 5, size: 4},
{value: 10, size: 7},
{value: 11, size: 8},
{value: 13, size: 9}
], 4)); // 23
动态规划
为了提高程序的运行效率,我们不妨将递归实现方式改成动态规划。这个问题有个专业的术语:0-1背包问题。0-1背包问题,dp解法历来都困扰很多初学者,大多人学一次忘一次,那么,这次我们努力💪将它记在心里。
注意,理解0-1背包问题的突破口,就是要理解 “0-1” 这个含义,这里对于每一件物品,要么带走(1),要么留下(0)。
基本思路
0-1背包问题子结构:选择一个给定第 i 件物品,则需要比较选择第 i 件物品的形成的子问题的最优解与不选择第 i 件物品的子问题的最优解。分成两个子问题,进行选择比较,选择最优的。
若将 f[i][w] 表示前 i 件物品恰放入一个容量为 w 的背包可以获得的最大价值。则其状态转移方程便是:
f[i][w] = max{ f[i-1][w], f[i-1][w-w[i]]+v[i] }
其中,w[i] 表示第 i 件物品的重量,v[i] 表示第 i 件物品的价值。
function knapsack (capacity, objectArr) {
var n = objectArr.length;
var f = [];
for (var i = 0; i <= n; i++) {
f[i] = [];
for (var w = 0; w <= capacity; w++) {
if (i === 0 || w === 0) {
f[i][w] = 0;
} else if (objectArr[i - 1].size <= w) {
var size = objectArr[i - 1].size,
value = objectArr[i - 1].value
f[i][w] = Math.max(f[i - 1][w - size] + value, f[i - 1][w]);
} else {
f[i][w] = f[i - 1][w];
}
}
}
return f[n][capacity];
}
以上方法空间复杂度和时间复杂都是O(nm),其中 n 为物品个数,m 为背包容量。时间复杂度没有优化的余地了,但是空间复杂我们可以优化到O(m)。首先我们要改写状态转移方程:
f[w] = max{ f[w], f[w-w[i]]+v[i] }
function knapsack (capacity, objectArr) {
var n = objectArr.length;
var f = [];
for (var w = 0; w <= capacity; w++) {
for (var i = 0; i < n; i++) {
if (w === 0) {
f[w] = 0;
} else if (objectArr[i].size <= w) {
var size = objectArr[i].size,
value = objectArr[i].value
f[w] = Math.max(f[w - size] + value, f[w] || 0);
} else {
f[w] = Math.max(f[w] || 0, f[w - 1]);
}
}
}
return f[capacity];
}