动态规划:零钱兑换问题(javascript求解)

前言

昨天天面试时突然提到一嘴动态规划,结果面试官问我动态规划是啥子,我竟然一时表达不出来,在这里用自己的话重复在练习一下:动态规划就是先找到大问题的子问题,将子问题的解作为中间结果递推求解最终问题,而且子问题的解要是局部最优的。

动态规划问题的一般解题步骤

  1. 确定状态:一般是可以用数组表示状态
  2. 化成子问题:涉及到问题的最后一步怎么求解的(比如跳台阶问题的最后一般可以调上一节台阶和跳上2节台阶得到 dp[n] = dp[n-1]+dp[n-2]
  3. 初始条件和边界情况需要注意 (设置 dp[0] = 1)
  4. 计算顺序 自底向上 (递归的话一般是自顶向下的顺序)

零钱兑换

题目描述

给定不同面额的硬币 coins 和一个总金额
amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。

你可以认为每种硬币的数量是无限的。

示例 1:

输入:coins = [1, 2, 5], amount = 11
输出:3
解释:11 = 5 + 5 + 1

思路:

  • 先确定用dp[amount+1]数组表示使用的最小的硬币数量
  • 最后一步无论怎么样都是添加一个硬币coin使得金额刚好是amount ,然后在比较添加这枚和不添加这枚的硬币数谁少, 所以递推式min(dp[amount - coin] +1,dp[amount])
  • 返回结果 dp[amount]

js代码

var coinChange = function (coins, amount) {
// 用无穷大填充数组的每一个元素
  let dp = new Array(amount + 1).fill(Infinity)
  dp[0] = 0
  console.log(dp);
  let len = coins.length;

  for (let i = 0; i <= amount; i++) {
    for (let j = 0; j < len; j++) {
    // 注意:这里的条件左边是钱的价格肯定要比硬币的价额大才可以放进去的
    // 条件的右边是如果数组元素还是初始值肯定就是没有符合条件的硬币放
      if (i >= coins[j] && dp[i - coins[j]] !== Infinity) {
        dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1)
      }
    }
  }
  if (dp[amount] === Infinity) {
    return -1;
  }
  return dp[amount];
};


console.log(coinChange([1, 2, 5], 11));

零钱兑换 II

题目描述

给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。

示例 1:

输入: amount = 5, coins = [1, 2, 5]
输出: 4
解释: 有四种方式可以凑成总金额:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1

思路

其实这题和调台阶其实是同一类型的题

完全背包之组合问题——填满容量为amount的背包,有几种硬币组合

  • dp[j] 代表装满容量为j的背包有几种硬币组合 转移方程:dp[j] = dp[j] + dp[j - coin]
  • 当前填满j容量的方法数 = 之前填满j容量的硬币组合数 + 填满j - coin容量的硬币组合数 也就是当前硬币coin的加入,可以把j -coin容量的组合数加入进来 和01背包差不多,唯一的不同点在于硬币可以重复使用,一个逆序一个正序的区别 返回dp[-1],也就是dp[amount]

上面是看了别人的思路,然后我一开始是这么写的代码

var change = function (amount, coins) {
  // 用dp[i]存储有多少当前金额i下有多少中组合方式
  let dp = new Array(amount + 1).fill(0);
  dp[0] = 1

  for (let i = 1; i <= amount; i++) {
    for (let j = 0; j < coins.length; j++) {

      dp[i] = dp[i - coins[j]] + dp[i]
    }
  }

  return dp[amount];

};


console.log(change(5, [1, 2, 5]))

然后一直不对 ,结果比真实答案要大,后来我debuger 后知道这是求的排列数的结果,而答案是求组合数 ,并且我在leetcode翻到如下一句话

如果求组合数就是外层for循环遍历物品,内层for遍历背包。
如果求排列数就是外层for遍历背包,内层for循环遍历物品。

所以要改变一下遍历顺序

var change = function (amount, coins) {
  // 用dp[i]存储有多少当前金额i下有多少中组合方式
  let dp = new Array(amount + 1).fill(0);
  dp[0] = 1

  for (let i = 0; i < coins.length; i++) {
    for (let j = coins[i]; j <= amount; j++) {
      dp[j] = dp[j] + dp[j - coins[i]]
    }
  }

  return dp[amount];

};


console.log(change(5, [1, 2, 5]))
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
动态规划(Dynamic Programming,DP)是一种常见的算法思想,用于解决一些具有重叠子问题和最优子结构性质的问题。最短路径问题就是其中一种。 最短路径问题指的是,给定一个有向图和起点/终点,求从起点到终点的最短路径。其中,最短路径可以定义为边权和最小的路径。 在JavaScript中,可以使用动态规划求解最短路径问题。下面以一个简单的示例来说明。 假设有一个有向图,如下所示: ``` 0 -> 1 (2) 0 -> 2 (5) 1 -> 2 (1) 1 -> 3 (6) 2 -> 3 (2) 2 -> 4 (4) 3 -> 4 (6) ``` 其中,括号内的数字表示边的权重。 我们可以使用一个一维数组`dist`来记录每个节点到起点的最短距离。初始时,起点到自己的距离为0,到其他节点的距离为无穷大(即不可达)。 ```javascript const graph = [ [0, 1, 2], [0, 2, 5], [1, 2, 1], [1, 3, 6], [2, 3, 2], [2, 4, 4], [3, 4, 6], ]; const start = 0; const n = 5; const dist = new Array(n).fill(Infinity); dist[start] = 0; ``` 接下来,我们需要使用动态规划来更新`dist`数组。具体而言,我们遍历每条边,如果发现通过该边可以使得到达目标节点的距离变短,则更新`dist`数组。 ```javascript for (let i = 0; i < n - 1; i++) { for (const [u, v, w] of graph) { if (dist[u] !== Infinity && dist[u] + w < dist[v]) { dist[v] = dist[u] + w; } } } ``` 这里的外层循环是用来控制更新次数的,因为最多只需要更新n-1次,就可以保证所有的最短距离都被计算出来。内层循环则是遍历每条边,判断是否可以更新目标节点的最短距离。 最后,我们可以输出`dist`数组,来获得每个节点到起点的最短距离。 ```javascript console.log(dist); // [0, 2, 3, 5, 9] ``` 这里的结果表示,起点到0的距离为0,到1的距离为2,到2的距离为3,到3的距离为5,到4的距离为9。 所以,以上就是使用动态规划求解最短路径问题的一个简单的示例。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值