day21 算法思想:JS中分治、贪心、回溯和动态规划

贪心和递归分治

Question:
找零问题的核心是在几种不同面值如 1、5、10 分的硬币中,用最少的枚数凑出针一个需要找零的钱数。
贪心(greedy)算法:
它的核心逻辑是我们先选择面值较大的来找,再逐渐选小面额的。为什么这里是从大到小,而不是从小到大呢?因为通常面值越大,用到的数量就越少。

function minCoinChange(coins, amount) {
  var change = [];
  var total = 0;
  for (let i = coins.length; i >= 0; i--) { // 从大到小循环
    var coin = coins[i];
    while (total + coin <= amount) { // 将硬币逐个加入,面值要小于商品价格
      change.push(coin);  // 将硬币加入到结果
      total += coin; // 将硬币累加到总数
    }
  }
  return change;
}

贪心是最简单的一种解决方案,
可是这种方案有两个核心问题:1、找不到答案。2、找不到最优解。
递归(recursion) 和 分治(divide and conquer)
递归的遍历类似树(tree) 形的数据结构,深度优先(DFS)的遍历顺序。
递归带来的问题:重叠子问题。去重
回溯和记忆函数
回溯(backtracking) 的思想
记忆函数创建一个备忘录(memoization),来起到在执行中去重的作用。
递推和动态规划
第一个是无后效性,第二个是最优子结构。后无效性是说,如我们上图中所示,一个顶点下的子问题分支上的问题之间,依赖是单向的,后续的决策不会影响之前某个阶段的状态。而最优子结构指的是子问题之间是相互独立的,我们完全可以只基于子问题路径前面的状态推导出来,而不受其它路径状态的影响。
在动态规划中,解决问题用的是状态转移方程

  1. 初始化状态
  2. 状态参数
    状态转移方程:
    其实就是递推公式的思考模式
function minCoinChange(coins, amount) {
  var dp = Array(amount + 1).fill(Infinity);  // 每种硬币需要多少 
  dp[0] = 0; // 找0元,需要0个硬币
  for (let coin of coins) { // 循环每种硬币
    for (let i = coin; i <= amount; i++) {  // 遍历所有数量
      dp[i] = Math.min(dp[i], dp[i - coin] + 1);  // 更新最少需要用到的面值
    }
  }
  return dp[amount] === Infinity ? -1 : dp[amount];  // 如果最后一个是无限的,没法找 
}

延伸:位运算符
按位与(& AND)表示的是,对于每一个比特位,如果两个操作数相应的比特位都是 1 时,结果则为 1,否则为 0。 比如 5&9 的运算结果就是 0001,也就是 1。

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 // 5
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1 // 9
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 // 5&9

按位或(I OR)来说,针对每一个比特位,当两个操作数相应的比特位至少有一个 1 时,结果为 1,否则为 0。比如 5|9 的运算结果就是 1 1 0 1,也就是 13。


0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 // 5
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1 // 9
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 1 // 5|9

对于按位非(~ NOT)来说,指的则是反转操作数的比特位,即 0 变成 1,1 变成 0。那么~5 的运算结果就是 -6,~9 的运算结果就是 -10。

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 // 5
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 0 // -5
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1 // 9
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 0 // -9

按位异或(^ XOR),它的操作是对于每一个比特位,当两个操作数相应的比特位有且只有一个 1 时,结果为 1,否则为 0。那么 5^9 的运算结果就是 1 1 0 0,也就是 12。


0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 // 5
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1 // 9
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 // 5^9

再来看看位移。这里又分为左移(<< Left shift),有符号右移(>> Right shift)和无符号右移(>>> Zero-fill right shift)。左移会将 a 的二进制形式向左移 b (< 32) 比特位,右边用 0 填充,所以 9<<1 的结果就是 18。而有符号右移则会将 a 的二进制表示向右移 b (< 32) 位,丢弃被移出的位,所以 9>>>1 的结果就是 4。最后无符号右移会将 a 的二进制表示向右移 b (< 32) 位,丢弃被移出的位,并使用 0 在左侧填充,所以 9>>>1 的结果就是 2147483643。

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1 // 9      
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1 0 // 9 << 1
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 // 9 >> 1
0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 // 9 >>> 1

day21 算法思想:JS中分治、贪心、回溯和动态规划_第1张图片

极客时间《Jvascript进阶实战课》学习笔记 Day21

你可能感兴趣的:(前端javascript)