# 还在用现金支付吗

还在用现金支付吗

作者的思路是枚举每个硬币出现的次数,暴力计算:

function coinChange() {
  const counts500 = range(0, 2)
  const counts100 = range(0, 10)
  const counts50 = range(0, 15)
  const counts10 = range(0, 15)
  let result = 0
  for (const count500 of counts500) {
    for (const count100 of counts100) {
      for (const count50 of counts50) {
        for (const count10 of counts10) {
          if (count500 + count100 + count50 + count10 <= 15 && count500 * 500 + count100 * 100 + count50 * 50 + count10 * 10 === 1000)
            result++
        }
      }
    }
  }
  return result
}

function range(start: number, end: number, step = 1) {
  const result: number[] = []
  for (let i = start; i <= end; i += step)
    result.push(i)

  return result
}

但是事情没那么简单,我们可以看到LeetCode上一道非常相似的问题。书中的题目其实比LeetCode的题目还难一点,因为限制了最大的coin数量。

我们按照动态规划的思路实现如下:

function coinChange() {
  // 为了节约时间和空间,这里硬币都除了他们的最大公约数10
  // dp[i][j] coin价值总和为i coin数量为j 这种情况下的总的方案数
  const dp = new Array<number[]>(101)
  for (let i = 0; i < dp.length; i++)
    dp[i] = new Array<number>(16).fill(0)

  dp[0][0] = 1
  const coins = [1, 5, 10, 50]
  for (const coin of coins) {
    for (let i = coin; i < dp.length; i++) {
      for (let j = 1; j < 16; j++)
        dp[i][j] += dp[i - coin][j - 1]
    }
  }

  return dp[dp.length - 1].reduce((sum, item) => sum + item, 0)
}

在进一步,书中给出的coin总价值、coin数量、coin面值都是硬编码的,我们可以进一步推广:

function coinChange(value: number, count: number, originCoins: number[]) {
  const coinGCD = gcdList([...originCoins, value])
  const coins = originCoins.map(item => item / coinGCD)
  value /= coinGCD
  const dp = new Array<number[]>(value + 1)
  for (let i = 0; i < dp.length; i++)
    dp[i] = new Array<number>(count + 1).fill(0)

  dp[0][0] = 1

  for (const coin of coins) {
    for (let i = coin; i < dp.length; i++) {
      for (let j = 1; j < count + 1; j++)
        dp[i][j] += dp[i - coin][j - 1]
    }
  }

  return dp[dp.length - 1].reduce((sum, item) => sum + item, 0)
}

function gcdList(nums: number[]) {
  let result = nums[0]
  for (let i = 1; i < nums.length; i++) {
    result = gcd(result, nums[i])
    if (result === 1)
      break
  }
  return result
}

function gcd(a: number, b: number) {
  if (a < b) {
    const tmp = a
    a = b
    b = tmp
  }
  while (b !== 0) {
    const tmp = a % b
    a = b
    b = tmp
  }
  return a
}