JavaScript动态规划的实现

/**
 * 动态规划
 * 一个模型 三个特征
 * 一个模型:动态规划适合解决的问题的模型:“多阶段决策最优解模型”
 * 三个特征:1、最优子结构(可以通过问题的子问题的最优解推导出问题的最优解)、无后效性、重复子问题
 * 解决动态规划的两种思路:1、状态转移表法和状态转移方程
 * 1、状态转移表:拿到问题的时候,定义状态,每个状态代表一个节点,然后画出递归树,从递归树中就可以看出来,
 * 是否存在重复子问题。然后画出一个状态表,每个状态包含三个变量,行、列、数组值。
 * 根据决策的先后过程,从前往后,根据递推关系,分阶段填充状态表中的每个状态,然后翻译成代码。
 * 
 * 2、状态转移方程:需要分析某个问题是如何通过子问题来递归求解,根据最优子结构写出地推公式。
 * 
 */








/**
 * 01背包
 * 对于一组不同重量,不可分割的物品,需要选一些装入背包,在满足背包
 * 最大重量限制的前提下,背包中的物品总重量的最大值是多少
 * 
 * 
 * 动态规划的01背包:把整个求解过程分为n个阶段,每个阶段决策一个物品是否放到背包中。
 * 然后记录每一个阶段的状态的集合(去掉重复的),然后通过当前阶段的状态的集合 来推导下一个阶段的状态的集合,动态的往前推进
 * 比如 一个物品的重量分别是2,2,4,6,3
 * 那么每次选择放或者不放的情况就像下面这样
 *                                                 (0,0)
 *                  (1,0)                                                    (1,2)
 *          (2,0)                      (2,2)                     (2,2)                     (2,4)
 *   (3,0)        (3,4)          (3,2)      (3,6)          (3,2)       (3,6)       (3,4)         (3,8)
 * (4,0) (4,6)  (4,4) (4,10)  (4,2) (4,8)  (4,6) (4,14) (4,2) (4,10) (4,6) (4,12) (4,4)(4,10) (4,8) (4,14)
 * ....第五个太多了 以此类推
 * 
 * 接下来 遍历每一层的时候 就把重复的节点合并,只记录不同的状态,来保证每一层不同的状态不会超过w个。
 * 用一个二维数组来记录每层可以达到的不同状态
 * 我们这里一共有5个物品,背包的总重量为9,所以列数为9,确保每次决策完背包的重量不超过9
 * 初始状态的数组如下(下标从0开始,上面的序号依次减1)
 *   0 1 2 3 4 5 6 7 8 9
 * 0 0 0 0 0 0 0 0 0 0 0
 * 1 0 0 0 0 0 0 0 0 0 0
 * 2 0 0 0 0 0 0 0 0 0 0
 * 3 0 0 0 0 0 0 0 0 0 0
 * 4 0 0 0 0 0 0 0 0 0 0
 * 
 * 当决策完第一个的时候 数组如下(下标从0开始,上面的序号依次减1)
 *   0 1 2 3 4 5 6 7 8 9
 * 0 1 0 1 0 0 0 0 0 0 0
 * 1 0 0 0 0 0 0 0 0 0 0
 * 2 0 0 0 0 0 0 0 0 0 0
 * 3 0 0 0 0 0 0 0 0 0 0
 * 4 0 0 0 0 0 0 0 0 0 0
 * 
 * 当决策完第二个物品后的数组这样(2,0)(2,2)(2,2)(2,4)把重复的(2,2)只存一次,所以如下;(下标从0开始,上面的序号依次减1)
 *   0 1 2 3 4 5 6 7 8 9
 * 0 1 0 1 0 0 0 0 0 0 0
 * 1 1 0 1 0 1 0 0 0 0 0
 * 2 0 0 0 0 0 0 0 0 0 0
 * 3 0 0 0 0 0 0 0 0 0 0
 * 4 0 0 0 0 0 0 0 0 0 0 
 * 当决策完第三个物品后的数组这样如下;(下标从0开始,上面的序号依次减1)
 *   0 1 2 3 4 5 6 7 8 9
 * 0 1 0 1 0 0 0 0 0 0 0
 * 1 1 0 1 0 1 0 0 0 0 0
 * 2 1 0 1 0 1 0 1 0 1 0
 * 3 0 0 0 0 0 0 0 0 0 0
 * 4 0 0 0 0 0 0 0 0 0 0 
 * 当决策完四个物品后的数组这样如下;(下标从0开始,上面的序号依次减1)
 *   0 1 2 3 4 5 6 7 8 9
 * 0 1 0 1 0 0 0 0 0 0 0
 * 1 1 0 1 0 1 0 0 0 0 0
 * 2 1 0 1 0 1 0 1 0 1 0
 * 3 1 0 1 0 1 0 1 0 1 0
 * 4 0 0 0 0 0 0 0 0 0 0 
  * 当决策完五个物品后的数组这样如下;(下标从0开始,上面的序号依次减1)
 *   0 1 2 3 4 5 6 7 8 9
 * 0 1 0 1 0 0 0 0 0 0 0
 * 1 1 0 1 0 1 0 0 0 0 0
 * 2 1 0 1 0 1 0 1 0 1 0
 * 3 1 0 1 0 1 0 1 0 1 0
 * 4 1 0 1 1 1 1 1 1 1 1 
 * 
 * 所以可以看到此时最后一种就是最大物品重量和的情况了
 */
 function knapsack(){
    let items = [
        {
            name:'苹果',
            weight:2,
            value:5
        },
        {
           name:'雪梨',
           weight:2,
           value:2
       },
       {
           name:'草莓',
           weight:4,
           value:4,
       },
       {
           name:'波罗蜜',
           weight:6,
           value:2
       },
       {
           name:'皇帝柑',
           weight:3,
           value:10
       }
    ]
   let bagWeight = 9;//背包的容量
   let state =[];//创建一个state数组
    for(let i = 0;i<5;i++){
        let array = [];
        for(j=0;j<10;j++){
            array[j] = false;
        }
        state.push(array);
    }
 
    state[0][0] = true;//第一行的数据 不放进包里
    if(items[0].weight <= bagWeight){
        state[0][items[0].weight] = true;//第一行的数据 选择放进包里
    }
    for(let i = 1;i<items.length;i++){
        for(let j = 0;j<=bagWeight;j++){//当前物品不放进背包,但是要把上一层的状态拷贝到当前层 这个很重要
            if(state[i-1][j]){
                state[i][j] = state[i-1][j]
            }
        }
        for(let j = 0;j<=bagWeight - items[i].weight;j++){
            //把当前物品放进背包,但是不能超过背包的容量
            if(state[i-1][j]){
                state[i][j+items[i].weight] = true;//把当前物品标志
            }
        }
    }
   for(let i = bagWeight;i>=0;i--){//输出结果  最大的肯定是在后面,所以从后面算起
       if(state[items.length -1][i]){//所有物品决策完 结果肯定是在最后一行
           return i;
       }
   }

   return 0;
}

console.log( knapsack())
function knapsack2(items,w){
    let state = new Array(w+1).fill(false);
    state[0] = true;//第一个物品的不选择
    if(items[0]<=w){
        state[items[0]] = true;//第一个物品的选择
    }
    for(let i = 1;i<items.length;i++){
        for(let j = w-items[i];j>=0;--j){
            if(state[j]){
                state[j+items[i]] = true//放进去
            }
        }
    }
 for(let i = w;i>=0;i--){
     if(state[i]){
         return i;
     }
 }
}
console.log( knapsack2([2,2,4,6,3],9))



/**
 * 01背包物品带价值计算。
 * 对于一组不同重量、不同价值、不可分割的物品,在满足背包最大重量限制的前提下,背包中可装入的物品总
 * 价值最大是多少。
 * 在递归树中,在背包中物品总重量一样的情况下,节点对应的物品总价值更大,就选择这个节点,然后沿着这个决策路线
 * 往下决策就可以。
 * 也就是说对于(i,cw)相同的不同节点,只需要保留cv更大的那个节点,
 */
 function knapsackValue() {
  
    let items = [
        {
            name:'苹果',
            weight:2,
            value:5
        },
        {
           name:'雪梨',
           weight:2,
           value:2
       },
       {
           name:'草莓',
           weight:4,
           value:4,
       },
       {
           name:'波罗蜜',
           weight:6,
           value:2
       },
       {
           name:'皇帝柑',
           weight:3,
           value:10
       }
    ]
   let bagWeight = 9;//背包的容量
   let state =[];//创建一个state数组
    for(let i = 0;i<items.length;i++){
        let array = [];
        for(j=0;j<=bagWeight;j++){
            array[j] = -1;
        }
        state.push(array);
    }
    state[0][0] = 0;
    if(items[0].weight<=bagWeight){
        state[0][items[0].weight] = items[0].value
    }
    for(let i = 1;i<items.length;i++){
        for(let j = 0;j<=bagWeight;j++){//不放当前物品 就把上一层的状态复制过来
            if(state[i-1][j]>=0){
                state[i][j] = state[i-1][j];
            }
        }
        for(let j = 0;j<=bagWeight-items[i].weight;j++){
            if(state[i-1][j]>=0){
                let value = state[i-1][j]+items[i].value
                if(value > state[i][j+items[i].weight]){
                    state[i][j+items[i].weight] = value
                }
            }
        }
    }

    let maxValue = -1;
    for(let j = 0;j<=bagWeight;j++){
        if(state[items.length-1][j]>maxValue){
            maxValue = state[items.length-1][j]
        }
    }
    return maxValue
}
console.log(knapsackValue())
/**
 * 
 * 完全背包
 * 有N件物品和一个容量是V的背包。每个物品只能使用一次,每种物品有无限件可以使用。
 * 第i件物品的体积是wi,价值是vi。求将那些物品装入背包,可以使这些物品的总体积不超过背包容量、
 * 且总价值最大呢?
 * 请输出最大价值。
 * 
 * 与01背包不一样的是,物品有无限件可以使用,那就不能根据物品一个个的来决策构造二叉树,从而构造状态表了。
 * 换一个思路,我们的节点仍然使用state[i][j]来表示,在物品[0,i]中,不超过指定背包重量j,获得的最大价值。
 * 那么每次我们怎么选择呢?
 * 我们换做从物种来选择,每次选择一个物品进入背包的时候,都是从[0,i]这么多物种中,选择一个,然后选择价值最大的一个。
 * 
 * 假设 物种重量数组是 [2,2,4,6,3],价值数组是[5,2,4,2,10],背包重量是9
 * 
 * i = 0的时候,选择物品0,
 * 
 * 个数如下
 *    0  1  2  3  4  5  6  7  8  9
 * 0  0  0  1  0  2  0  3  0  4  0
 * 
 * 加上对应的价值就是下面这样
 *    0  1  2  3  4   5  6   7  8  9
 * 0  0  0  5  0  10  0  15  0  20  0 
 * 
 * 当i=1的时候,代表可以选择下标为1这两个物品 ,

 * 加上价值就是这样
  *    0  1  2  3  4   5  6   7  8  9
 * 0   0  0  5  0  10  0  15  0  20  0 
 * 1   0  0  5  2  10  4  15  6  20  8  
 * 
 * 依次类推,得到矩阵如下:
 *     0  1  2  3  4   5  6   7  8  9
 * 0   0  0  5  0  10  0  15  0  20  0 
 * 1   0  0  5  2  10  4  15  6  20  8  
 * 2   0  0  5  2  10  4  15  6  20  8
 * 3   0  0  5  2  10  4  15  6  20  8
 * 4   0  0  5  10 10  15 20  20 25  30 
 * 
 * 
 * 
 * 可以看到
 * 1、当i>=1的时候,选取的个数i要满足i*weights[i]<=maxWeight
 * 2、每次选取第i个物品的时候,逐个的去选取k个物品,其中k*weight[i]<=j,j是当前背包的内容
 * 3、每次决策一个节点的时候有 当前选取k个i物品对应的价值,还有不选取k个物品对应的价值
 * 其中选取k个i物品对应的价值 = state[i-1][j-k*weights[i]]+k*values[i];
 * 其中,j-k*weights[i]代表 没装k个i物品之前背包的容量,再加上k*values[i],表示装入k个i物品
 * 
 * 不选取就是 state[i][j],什么都不做
 * 
 * 所以 state[i][j] = Math.max(state[i][j],state[i-1][j-k*weights[i]]+k*values[i])
 * 
 */

 function CompletePack(weights,values,maxWeight){
    //先初始化状态数组
    let n = weights.length;
    let state = new Array(n+1);
    for(let i = 0;i<n;i++){
        state[i] = new Array(maxWeight+1).fill(0);
    }
    //初始化第一行 枚举第一个物品可以选择的个数 然后赋值
    for(let i = 0;i * weights[0]<=maxWeight;i++){
        state[0][i*weights[0]] = i*values[0]
    }
    for(let i = 1;i<n;i++){
        for(let j = 0;j<=maxWeight;j++){
            for(let k = 0;k*weights[i]<=j;k++){
                //枚举下标为i的物品 可以选择的个数
                 state[i][j] = Math.max(state[i][j],state[i-1][j-k*weights[i]]+k*values[i])
            }
         }
    }
    return state[n-1][maxWeight]
}










/**
 * 计算购物车中
 * 比如“满 200 元减 50 元”。假设购物车中有 n 个(n>100)想买的商品,希望从里面选几个,
 * 在凑够满减条件的前提下,让选出来的商品价格总和最大程度地接近满减条件(200 元)
 * 
 */

 function double11advance(items,w) {
    let state = [];
    for(let i = 0;i<items.length;i++){
        let array = [];
        for(let j = 0;j<=2*w;j++){
            array[j] = false
        }
        state[i] = array;
    }
    state[0][0] = true;
    if(items[0].value<=2*w){
        state[0][items[0].value] = true
    }
    for(let i = 1;i<items.length;i++){
        for(let j = 0;j<=2*w;j++){
            if(state[i-1][j]){
                state[i][j] = state[i-1][j];
            }
        }
        for(let j = 0;j<=2*w - items[i].value;j++){
            if(state[i-1][j]){
                state[i][j+items[i].value] = true
            }
        }
    }
    let j;
    for(j = w;j<2*w;j++){
        if(state[items.length-1][j]) break;
    }
    if(j == 2*w) return ;
    for(let i = items.length-1;i>=1;i--){
        if(j-items[i].value>=0 && state[i-1][j-items[i].value]){
            console.log(items[i],'买')
            j = j-items[i].value;
        }
    }
    if(j!=0) console.log(items[0],'买')
    console.log(state)
}
let items = [
    {
        name:'货品1',
        value:20,
    },
    {
        name:'货品2',
        value:40,
    },
    {
        name:'货品3',
        value:50,
    },
    {
        name:'货品4',
        value:20,
    },
    {
        name:'货品5',
        value:80,
    },
    {
        name:'货品6',
        value:30,
    },
    {
        name:'货品7',
        value:45,
    }
]
console.log(double11advance(items,200))


/**
 * 假设我们有一个n*n的矩阵,存储的都是正整数。现在要从矩阵的左上角走到矩阵的右下角,计算最短路径长度
 * 
 *   0 1 2 3
 * 0 1 3 5 9
 * 1 2 1 3 4
 * 2 5 2 6 7
 * 3 6 8 4 3
 * 
 * 先用回溯解决这个问题
 * 
 * 思路-》回溯解决 画出递归树-》找出子问题-》翻译成代码
 * 
 */
 function getMiniDist(){
    let minDist = 9999;
    let array = [
        [1,3,5,9],
        [2,1,3,4],
        [5,2,6,7],
        [6,8,4,3]
    ];
    miniDistBt(0,0,0,array)
    function miniDistBt(i,j,dist,w) {
        if(i == w.length-1 && j == w.length-1){
            if(dist<minDist) {
                minDist = dist;
                return;
            }
        }
        if(i < w.length-1){
            //往下走
            miniDistBt(i+1,j,dist+w[i][j],w);
        }
        if(j<w.length-1){
            //往右走
            miniDistBt(i,j+1,dist+w[i][j],w);
        }
    }
    console.log(minDist)
    return minDist;
}
/**
 * 接下来 我们画出递归树
 *                                               (0,0,1)
 *                   (1,0,3)                                             (0,1,4)
 *         (2,0,8)           (1,1,4)                           (1,1,4)            (0,2,9)
 *    (3,0,14) (2,1,10)   (2,1,6)  (1,2,7)                (2,1,6)  (1,2,7)   (1,2,12)  (0,3,18)
 * .....以此类推
 * 
 * 那么 用一个状态表来存储这个过程,数组中的i,j代表当前位置,数组中的值代表起点到当前位置的最短路径。
 *   0  1  2  3
 * 0 1  4  9  18
 * 1 3  4  7  11
 * 2 8  6  12 18
 * 3 14 14 16 19
 * 
 * 所以最短路径就是19
 */
 function minDistDP(array){
    let state = [],sum = 0;
    for(let i = 0;i<array.length;i++){
        let temp = []
        for(let j = 0;j<array.length;j++){
            temp[j] = 0
        }
        state.push(temp)
        
    }
    for(let i = 0;i<array.length;i++){
        //初始化第一行数据
        sum += array[0][i];
        state[0][i] = sum;
    }
    sum = 0;
    for(let i = 0;i<array.length;i++){
        //初始化第一行数据
        sum += array[i][0];
        state[i][0] = sum;
    }
    let i = 0
    for( i = 1;i<array.length;i++){
        for(let j = 1;j<array.length;j++){
            let min = Math.min(state[i-1][j],state[i][j-1]);
            state[i][j] = array[i][j]+min;
        }
    }
    let n = state.length-1
    return state[n][n];

}
let array = [
        [1,3,5,9],
        [2,1,3,4],
        [5,2,6,7],
        [6,8,4,3]
    ];
minDistDP(array)


/**
 * 状态转移方程
 * 根据最优子结构 写出递归公式(状态转移方程)
 * 两种实现方法:1、递归加状态表;2、迭代递推
 * 就像上面的例子中 递归公式就是
 * minDist(i,j) = w[i][j] + Min(w[i-1][j],w[i][j-1])
 */

 function minDist(array){
    let mem = [],sum = 0,n = array.length;
    for(let i = 0;i<n;i++){
        let temp = []
        for(let j = 0;j<n;j++){
            temp[j] = 0
        }
        mem.push(temp)
        
    }
    return miniDistBt(n-1,n-1)
    function miniDistBt(i,j){
        if(i == 0 && j == 0){return array[0][0];}//递归终止条件
        if(mem[i][j] > 0 ) return mem[i][j];//已经遍历过了 直接返回
        let minLeft = Number.MAX_VALUE,minUp = Number.MAX_VALUE
        if(j-1>=0){
            minLeft = miniDistBt(i,j-1);//他左边的子节点的值
        }
        if(i - 1>=0){
            minUp = miniDistBt(i-1,j);//他上边的子节点的值
        }
        let cur = array[i][j] + Math.min(minLeft,minUp);
        mem[i][j] = cur;
        return cur;
    }
   

}
let array2 = [
        [1,3,5,9],
        [2,1,3,4],
        [5,2,6,7],
        [6,8,4,3]
    ];
console.log(minDist(array2),'minDIst')

/**
 * 计算两个字符串的莱温斯坦距离:
 * 字符串距离:将一个字符串转换成另一个字符串 ,需要最少编辑的操作数
 * 莱温斯坦距离:允许增加、删除、替换字符三个操作
 * 
 * 匹配两个字符串,当匹配到a[i]和b[j]
 * 1、当a[i] == b[j],那么直接匹配下一个字符a[i+1],b[j+1]
 * 2、当a[i] != b[j],那么可以有这几种情况
 * 
 * a、删除a[i]或者在b[j]前面添加一个字符,然后继续匹配,也就是变成了a[i+1] 是否与b[j]相等
 * b、删除b[j]或者在a[i]前面添加一个字符,然后继续匹配,也就是变成了a[i] 是否与b[j+1]相等
 * c、将a[i]替换成b[j],或者把b[j]替换成a[i]。然后继续匹配a[i+1]与b[j+1]
 * 
 * 我们先用代码实现上面的过程
 */
 function lwst(string1,string2){
    let n = string1.length-1,m = string2.length-1,minDist = Number.MAX_VALUE;
    lwstBack(0,0,0);
    function lwstBack(i,j,edist){
        if(i == n || j == m){
            //匹配完成了
            if(i<n){edist += (n - i);}
            if(j<m){edist += (m - j);}
            if(edist<minDist){minDist = edist;}
            return;
        }
        if(string1[i] == string2[j]){
            //匹配 继续匹配下一个字符
            lwstBack(i+1,j+1,edist);
        }else{
            lwstBack(i+1,j,edist+1);
            lwstBack(i,j+1,edist+1);
            lwstBack(i+1,j+1,edist+1)
        }
    }
    return minDist;

}
/**
 * 接下来我们画出递归树
 *                       (0,0,0)
 *                        (1,1,0)
 *      (2,1,1)             (1,2,1)            (2,2,1)
 *      (3,2,1)      (2,2,2) (1,3,2) (2,3,2)   (3,2,2)(2,3,2)(3,3,2)
 * 
 * 可以看到i,j是有不一样的,所以是有重复子问题的,edit不一样的时候就选最小的那个
 * 那么节点就变成了(i,j,minedist(i,j))
 * 
 * 可以知道 (i,j,minedist(i,j))这个节点有可能从三个地方过来分别是
 *  1、(i-1,j,min)
 *  2、(i,j-1,min)
 *  3、(i-1,j-1,min)
 * 
 * 那么当a[i] == b[j]是
 * 状态表 就变成了 min_edist(i-1,j)+1,min_edist(i,j-1)+1,min(i-1,j-1)
 * 三个值中的最小值
 * 当不等的时候就是
 * min_edist(i-1,j)+1,min_edist(i,j-1)+1,min(i-1,j-1)+1,三个值中的最小值
 * 那么表就变成下面这样 mitcmu mtacnu
 *   0 1 2 3 4 5 
 * 0 0 1 2 3 4 5
 * 1 1
 * 2 2
 * 3 3
 * 4 4
 * 5 5
 * 
 */
 function lwstDP(string1,string2){
    let n = string1.length-1,m = string2.length-1;
    let minedist = new Array(n+1).fill().map((item)=>new Array(m).fill(0)),sum = 0;

    //初始化第一行第一列
    for(let i = 0;i<=m;i++){
        if(string1[0] == string2[i]){
            minedist[0][i] = i;//将前面的全部删掉,edist = i
        }else if(i != 0){
            minedist[0][i] = minedist[0][i-1]+1;
        }else{
            minedist[0][i] = 1;//第一个不相等的时候 edist = 1
        }
    }
    for(let i = 0;i<=n;i++){
        if(string1[i] == string2[0]){
            minedist[i][0] = i;//将前面的全部删掉,edist = i
        }else if(i != 0){
            minedist[i][0] = minedist[i-1][0]+1;
        }else{
            minedist[i][0] = 1;//第一个不相等的时候 edist = 1
        }
    }
    for(let i=1;i<=n;i++){
        for(let j = 1;j<=m;j++){
            if(string1[i] == string2[j]){
                minedist[i][j] = Math.min(Math.min(minedist[i-1][j]+1,minedist[i][j-1]+1),minedist[i-1][j-1])
            }else{
                minedist[i][j] = Math.min(Math.min(minedist[i-1][j]+1,minedist[i][j-1]+1),minedist[i-1][j-1]+1)
            }
        }
    }
    return minedist[n][m]
} 




/**
 * 计算最长公共子串长度
 * 最长公共子串只允许增加、删除字符两种操作,
 * 匹配两个字符串,当匹配到a[i]和b[j]
 * 
 * 每个状态用(i,j,max_lcs)来记录当前最长公共子串长度。
 * 
 * 如果a[i]与b[j]互相匹配,将最大公共子串长度加1,继续考察a[i+1],b[j+1]
 * 如果不匹配 z最长公共子串长度不
 *  1、删除a[i],或者在b[i]前面插入一个a[i],继续考察a[i+1]和b[j]
 *  2、删除b[i],或者在a[i]前面插入一个b[j],继续考察a[i]和b[j+1]
 * 
 * 所以当前最大子串有可能有三个状态转移过来
 * 当a[i] == b[j]
 * max_lcs(i,j) = Max(max(i-1,j) ,max(i,j-1),max(i-1,j-1)+1)
 * 当a[i] != b[j]
 * max_lcs(i,j) = Max(max(i-1,j) ,max(i,j-1),max(i-1,j-1))
 * 
 * mitcmu mtacnu
 *   0 1 2 3 4 5
 * 0 1 
 * 1
 * 2
 * 3
 * 4
 * 5
 */
 function lcs(string1,string2){
    let n = string1.length-1,m = string2.length-1;
    let maxLcs = new Array(n+1).fill().map((item)=>new Array(m).fill(0)),sum = 0;

    //初始化第一行第一列
    for(let i = 0;i<=m;i++){
        if(string1[0] == string2[i]){
            maxLcs[0][i] = 1;//与第一个字符相等,最长子串为1
        }else if(i != 0){
            maxLcs[0][i] = maxLcs[0][i-1];
        }else{
            maxLcs[0][i] = 0;
        }
    }
    for(let i = 0;i<=n;i++){
        if(string1[i] == string2[0]){
            maxLcs[i][0] = 1;//与第一个字符相等,最长子串为1
        }else if(i != 0){
            maxLcs[i][0] = maxLcs[i-1][0];
        }else{
            maxLcs[i][0] = 0;
        }
    }
    for(let i = 1;i<=n;i++){
        for(let j = 1; j<= m;j++){
            if(string1[i] == string2[j]){
                maxLcs[i][j] = Math.max(Math.max(maxLcs[i-1][j],maxLcs[i][j-1]),maxLcs[i-1][j-1]+1)
            }else{
                maxLcs[i][j] = Math.max(Math.max(maxLcs[i-1][j],maxLcs[i][j-1]),maxLcs[i-1][j-1])
            }
        }
    }
    return maxLcs[n][m];
}
/**
 * “杨辉三角”不知道你听说过吗?我们现在对它进行一些改造。
 * 每个位置的数字可以随意填写,经过某个数字只能到达下面一层相邻的两个数字。
 * 假设你站在第一层,往下移动,我们把移动到最底层所经过的所有数字之和,定义为路径的长度。
 * 请你编程求出从最高层移动到最底层的最短路径长度。
 */



/**
 * 118. 杨辉三角
 * https://leetcode-cn.com/problems/pascals-triangle/
 */
 var generate = function(numRows) {
    //1、先把dp数组初始化了
    let result = new Array(numRows);
    for(let i = 0;i<numRows;i++){
        let sonArray = new Array(i+1);
        sonArray[0] = 1;
        sonArray[i] = 1;
        result[i] = sonArray
    }

    //2、回溯法 就是遍历 
    for(let i = 2;i<numRows;i++){
        for(let j = 1;j<i;j++){
            result[i][j] = result[i-1][j-1] + result[i-1][j]
        }
    }
    return result;
};
/**
 * 
119. 杨辉三角 II
https://leetcode-cn.com/problems/pascals-triangle-ii/
 */
var getRow = function(rowIndex) {
    let numRows = rowIndex+1
 let result = new Array(numRows);
    for(let i = 0;i<numRows;i++){
        let sonArray = new Array(i+1);
        sonArray[0] = 1;
        sonArray[i] = 1;
        result[i] = sonArray
    }

   
    for(let i = 2;i<numRows;i++){
        for(let j = 1;j<i;j++){
            result[i][j] = result[i-1][j-1] + result[i-1][j]
        }
    }
    return result[rowIndex];
};
/**
  * 322. 零钱兑换
  * https://leetcode-cn.com/problems/coin-change/
  * 
  * 1、首先定义状态,每个状态代表一个节点。
  * 那么这里怎么定义一个节点呢?
   * (i,nums)来定义一个节点,代表,组成金额i至少需要nums个硬币。
    * coins = [1, 2, 5], amount = 11
    * 比如amout = 11,那么一共有 0 1 2 3 4 5 6 7 8 9 10 11个节点
    * 其中state[0] = 0 ,因为组成0元面额,不需要硬币
    * state[1] = 1,组成1元面额 ,需要一个1元硬币
    * state[2] = 1,组成2元面额,需要一个2元硬币
    * state[3] = 2,组成3元面额,需要一个1元一个2元硬币
    * state[4] = 2,组成4元面额,需要2个2元硬币
    * state[5] = 1,组成5元面额,需要1个1元硬币
    * state[6] = 2,组成6元面额,需要1个1元硬币,1个5元硬币
    * state[7] = 2,组成7元硬币,需要1个2元硬币,1个5元硬币
    * state[8] = 3,组成8元硬币,需要1个1元、1个2元、1个5元硬币
    * state[9] = 3,组成9元硬币,需要2个2元硬币,1个5元硬币
    * state[10] = 2,组成10元硬币,需要2个5元硬币
    * state[11] = 3,组成1元硬币,需要2个5元硬币,1个1元硬币
    * 
    * 那么反过来是不是可以这样看
    * 求state[11]的时候,只需要看使用state[10]、state[9]、state[6] 三者最小的值再+1
    * 因为你选一个硬币,就是在当前硬币总金额基础上再选择一个硬币
    * 那么 state[i] = Min(state[i-coins(j)])+1
    *  
*/
    var coinChange = function(coins, amount) {
        let state = new Array(amount+1).fill(amount+1);
        state[0] = 0;
        for(let i = 1;i<=amount;i++){
            //组成i元需要的币数
            for(let j = 0;j<coins.length;j++){
                //每次 探索对应硬币面值
                if(i-coins[j]>=0 && state[i-coins[j]]!=amount+1){
                    //选取的硬币面值 只能小于等于当前金额 并且对应的金额基础已经赋值
                    state[i] = Math.min(state[i],state[i-coins[j]]+1)
                }

            } 
        }
        if(state[amount] == amount+1){
            return -1;
        }else{
            return state[amount];
        }
    };
    var coinChange3 = function(coins, amount) {
        //使用完全背包的方法:
        let n = coins.length;
        let state =  new Array(amount+1).fill(amount+1);
        state[0] = 0;
        for(let i = 0;i<n;i++){
            let coin = coins[i];
            for(let j = coin;j<=amount;j++){
                state[j] = Math.min(state[j],state[j-coin]+1)
            }
        }
        if(state[amount] == amount+1){
            return -1;
        }else{
            return state[amount]
        }
    };
/**
 * 338. 比特位计数
 * https://leetcode-cn.com/problems/counting-bits/
 * 首先利用回溯法把他所有打印出来,观察一下有没有什么规律
    0 '0'      0
    1 '1'      1
    2 '10'     1
    3 '11'     2
    4 '100'    1
    5 '101'    2
    6 '110'    2
    7 '111'    3
    8 '1000'   1
    9 '1001'   2
    10 '1010'  2
    11 '1011'  3
    12 '1100'  2
    13 '1101'  3
    14 '1110'  3
    15 '1111'  4
    16 '10000' 1
    17 '10001' 2
    18 '10010' 2
    19 '10011' 3
    20 '10100' 2

    观察有三个规律
    1、当i是2的幂次方时候,count = 1
    2、当i是奇数时,count = count(i-1)+1
    3、当i是偶数时,count = count(i/2)

    然后我在发现,只针对后面两个就可以判断了
    第一种解法时间复杂度只打败了27%,第二种是99.37%
    时间复杂度一下子提升上来了


 */
var countBits = function(n) {
        /**
         *  1、当i是2的幂次方时候,count = 1
            2、当i是奇数时,count = count(i-1)+1
            3、当i是偶数时,count = count(i/2)
        */
        // let result = [0];
        // for(let i = 1;i<=n;i++){
        //     let count = 0;
        //     if(i & (i-1) == 0){
        //         //2的幂次方
        //         count = 1;
        //     }else if(i%2 == 1){
        //         //奇数
        //         count = result[i-1]+1;
        //     }else{
        //         count = result[i/2];
        //     }
        //     result.push(count)
        // }
        // return result;


        let result = [0];
        for(let i = 1;i<=n;i++){
            let count = 0;
            if(i%2 == 1){
                //奇数
                 count = result[i-1]+1;
            }else{
                count = result[i/2];
            }
            result.push(count)
        }
        return result;
};


/**
 * 剑指 Offer II 088. 爬楼梯的最少成本
 * https://leetcode-cn.com/problems/GzCJIP/
 * 
 * 先来分析题目,我觉得关键是 “选择向上爬一个或者两个阶梯”还有“可以选择从下标为0或1的元素作为初始阶梯”

 * cost = [10, 15, 20]
 * 接下来使用动态规划试一下,每个阶梯选择走或者不走,
 * 看下对最后的结局有什么影响。
 * 每个节点 代表 当前阶梯 、当前步数、当前花费
 *                                          (0,0,0)
 *                     (1,0,0)                                 (1,1,10)
 *           (2,0,0)         (2,1,15)             (2,1,10)            (2,2,25)
 *       (3,0,0)(3,1,20)   (3,1,15) (3,2,35)      (3,1,10) (3,2,30)     (3,2,25)  (3,3,45)        
 * 
 * 在这个基础上,增加每次选择走当前阶梯的时候 走一步和走两步的结果,第一个节点属性代表所在的阶梯  
 * 节点的第一个子节点代表不选择走这个阶梯,第二个子节点 代表选择这个阶梯,但是只走一步,第二个子节点代表选择这个阶梯,并且选择走两步
 *                                                (0,0,0)
 *                       (0,0,0)                            (1,1,10)                       (2,2,10)                   
 * (0,0,0)        (1,1,15)        (2,2,15)             (1,1,10) (2,2,25)(3,3,25)             (2,2,10)(3,3,25)(4,4,25)
 *         (2,2,35)(3,3,35)    (3,3,35) (4,4,35)   ....
 * 
 * 依次类推,构造矩阵如下
 *   0 1 2
 * 0 0 
 * 1   10
 * 2     15
 *   
 *  state[i][j] = Math.min(state[i-1][j-1]+cost[i-1],state[i-2][j-2]+cost[i-2])                                       
 * 
 * 192 ms, 在所有 JavaScript 提交中击败了7.07%的用户
 * 
 * 虽然解决了 但是时间复杂度有点高 继续再优化一下看看啊
 * 
 * 尝试一下 只用一维的数组是否能解问题啊
 * 96 ms, 在所有 JavaScript 提交中击败了7.07%的用户
 * 
 */
 var minCostClimbingStairs = function(cost) {
    // let n = cost.length,result =new Array(n+1);
    // for(let i = 0;i<=n;i++){
    //     result[i] = new Array(n+1).fill(Number.MAX_SAFE_INTEGER);
    // }
    // //因为可以选择下标为0或者1的作为开始点
    // result[0][0] = 0;
    // result[1][1] = 0;

    // for(let i = 2;i<=n;i++){
    //     result[i][i] = Math.min(result[i-1][i-1]+cost[i-1],result[i-2][i-2]+cost[i-2]);
    // }
    // return result[n][n]




    let n = cost.length,result = new Array(n+1).fill(Number.MAX_SAFE_INTEGER);
    //因为可以选择下标为0或者1的作为开始点
    result[0] = 0;
    result[1] = 0;

    for(let i = 2;i<=n;i++){
        result[i] = Math.min(result[i-1]+cost[i-1],result[i-2]+cost[i-2]);
    }
    return result[n];

};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值