|
| 1 | +# 练习答案与提示 |
| 2 | + |
| 3 | +> 说明:以下为参考答案/提示,不是唯一解。为了保持可读性,示例代码尽量短,省略了异常处理。 |
| 4 | +
|
| 5 | +## 第二章 计算 |
| 6 | + |
| 7 | +**练习 1:懒求值下的乘法次数** |
| 8 | + |
| 9 | +`pow5(x)` 展开是 `x*x*x*x*x`,需要 4 次乘法。如果参数不先求值,每一次出现都会重新计算。 |
| 10 | + |
| 11 | +设 `M(n)` 表示 `pow5` 嵌套 n 次的乘法次数: |
| 12 | + |
| 13 | +* `M(1) = 4` |
| 14 | +* `M(n) = 4 + 5 * M(n-1)` |
| 15 | + |
| 16 | +因此:`M(2)=24`,`M(3)=124`,`M(4)=624`。 |
| 17 | + |
| 18 | +**练习 2:强制求值的问题** |
| 19 | + |
| 20 | +* 可能做很多无用计算(性能浪费)。 |
| 21 | +* 不能处理“无穷序列/无穷结构”。 |
| 22 | +* 即使分支不会被用到,也提前执行(丢失短路优势)。 |
| 23 | +* 如果参数包含副作用,提前执行会改变行为。 |
| 24 | + |
| 25 | +## 第三章 过程 |
| 26 | + |
| 27 | +**阶乘(循环/递归/尾递归)** |
| 28 | + |
| 29 | +```javascript |
| 30 | +function fact(n) { |
| 31 | + let r = 1; |
| 32 | + for (let i = 2; i <= n; i++) r *= i; |
| 33 | + return r; |
| 34 | +} |
| 35 | + |
| 36 | +function factRec(n) { |
| 37 | + return n <= 1 ? 1 : n * factRec(n - 1); |
| 38 | +} |
| 39 | + |
| 40 | +function factTail(n, acc = 1) { |
| 41 | + return n <= 1 ? acc : factTail(n - 1, acc * n); |
| 42 | +} |
| 43 | +``` |
| 44 | + |
| 45 | +**斐波那契(循环/递归/尾递归)** |
| 46 | + |
| 47 | +```javascript |
| 48 | +function fib(n) { |
| 49 | + let a = 0, b = 1; |
| 50 | + for (let i = 0; i < n; i++) { |
| 51 | + [a, b] = [b, a + b]; |
| 52 | + } |
| 53 | + return a; |
| 54 | +} |
| 55 | + |
| 56 | +function fibRec(n) { |
| 57 | + return n <= 1 ? n : fibRec(n - 1) + fibRec(n - 2); |
| 58 | +} |
| 59 | + |
| 60 | +function fibTail(n, a = 0, b = 1) { |
| 61 | + return n === 0 ? a : fibTail(n - 1, b, a + b); |
| 62 | +} |
| 63 | +``` |
| 64 | + |
| 65 | +**短路求值说明** |
| 66 | + |
| 67 | +`condition ? expr1 : expr2` 只会计算其中一个分支。 |
| 68 | +所以当 `y == 1` 时,`pow(x, y-1) * x` 不会被执行。 |
| 69 | + |
| 70 | +**顺序/分支/循环的现实过程** |
| 71 | + |
| 72 | +示例: |
| 73 | + |
| 74 | +* 顺序:起床 → 洗漱 → 早餐。 |
| 75 | +* 分支:是否下雨?下雨带伞,否则不带。 |
| 76 | +* 循环:刷牙 2 分钟(重复动作)。 |
| 77 | + |
| 78 | +## 第四章 编码 |
| 79 | + |
| 80 | +**小数的表示** |
| 81 | + |
| 82 | +`1/3` 在十进制是无限循环小数,到了二进制依然是无限循环。IEEE 754 用“符号位 + 指数 + 尾数”近似表示,所以会出现精度误差。 |
| 83 | + |
| 84 | +可以用 `0.1 + 0.2` 观察误差: |
| 85 | + |
| 86 | +```javascript |
| 87 | +0.1 + 0.2; // 0.30000000000000004 |
| 88 | +``` |
| 89 | + |
| 90 | +## 第五章 序列 |
| 91 | + |
| 92 | +**LCS 提示** |
| 93 | + |
| 94 | +经典解法是动态规划: |
| 95 | + |
| 96 | +``` |
| 97 | +if a[i-1] == b[j-1] => dp[i][j] = dp[i-1][j-1] + 1 |
| 98 | +else => dp[i][j] = max(dp[i-1][j], dp[i][j-1]) |
| 99 | +``` |
| 100 | + |
| 101 | +这要求你能“多次访问任意位置”,所以在 C++ 中至少需要 Random Access 迭代器。 |
| 102 | + |
| 103 | +## 第六章 数据 |
| 104 | + |
| 105 | +**sumList** |
| 106 | + |
| 107 | +```javascript |
| 108 | +function sumList(list) { |
| 109 | + let sum = 0; |
| 110 | + for (let p = list; p !== null; p = p.next) sum += p.value; |
| 111 | + return sum; |
| 112 | +} |
| 113 | +``` |
| 114 | + |
| 115 | +**filterList** |
| 116 | + |
| 117 | +```javascript |
| 118 | +function filterList(list, pred) { |
| 119 | + if (list === null) return null; |
| 120 | + if (pred(list.value)) { |
| 121 | + return { value: list.value, next: filterList(list.next, pred) }; |
| 122 | + } |
| 123 | + return filterList(list.next, pred); |
| 124 | +} |
| 125 | +``` |
| 126 | + |
| 127 | +**树遍历** |
| 128 | + |
| 129 | +参考正文中的 `preorder/inorder/postorder`。 |
| 130 | + |
| 131 | +**DFS/BFS** |
| 132 | + |
| 133 | +参考正文中的 `dfs/bfs`,DFS 使用栈,BFS 使用队列。 |
| 134 | + |
| 135 | +## 第七章 状态 |
| 136 | + |
| 137 | +**计数器** |
| 138 | + |
| 139 | +```javascript |
| 140 | +let counter = { |
| 141 | + value: 0, |
| 142 | + inc() { this.value += 1; }, |
| 143 | + dec() { this.value -= 1; } |
| 144 | +}; |
| 145 | +``` |
| 146 | + |
| 147 | +**电梯状态机** |
| 148 | + |
| 149 | +状态示例:`idle / up / down / open`,转移规则取决于请求队列。 |
| 150 | + |
| 151 | +**move** |
| 152 | + |
| 153 | +```javascript |
| 154 | +function move(point, dx, dy) { |
| 155 | + return { x: point.x + dx, y: point.y + dy }; |
| 156 | +} |
| 157 | +``` |
| 158 | + |
| 159 | +**共享状态与库存示例(提示)** |
| 160 | + |
| 161 | +把“读库存 + 减库存”拆成两步,再用 `await` 人为制造切换点,就能看到负库存或丢失更新的情况: |
| 162 | + |
| 163 | +```javascript |
| 164 | +let stock = 1; |
| 165 | +async function buy() { |
| 166 | + let current = stock; |
| 167 | + await Promise.resolve(); |
| 168 | + stock = current - 1; |
| 169 | +} |
| 170 | +``` |
| 171 | + |
| 172 | +## 第八章 引用 |
| 173 | + |
| 174 | +**数组输出** |
| 175 | + |
| 176 | +`a` 和 `b` 指向同一数组,`c` 是浅拷贝: |
| 177 | + |
| 178 | +```javascript |
| 179 | +// 输出: |
| 180 | +// a: [1, 2, 3] |
| 181 | +// b: [1, 2, 3] |
| 182 | +// c: [1, 2] |
| 183 | +``` |
| 184 | + |
| 185 | +**cloneUser(浅拷贝)** |
| 186 | + |
| 187 | +```javascript |
| 188 | +const cloneUser = user => ({ ...user }); |
| 189 | +``` |
| 190 | + |
| 191 | +**freeze 示例** |
| 192 | + |
| 193 | +```javascript |
| 194 | +let obj = Object.freeze({ x: 1 }); |
| 195 | +obj.x = 2; |
| 196 | +obj.x; // 1 |
| 197 | +``` |
| 198 | + |
| 199 | +**共享尾部链表(提示)** |
| 200 | + |
| 201 | +两个链表可以共用同一个尾节点: |
| 202 | + |
| 203 | +```javascript |
| 204 | +let tail = node(3, null); |
| 205 | +let listA = node(1, node(2, tail)); |
| 206 | +let listB = node(9, tail); |
| 207 | +``` |
| 208 | + |
| 209 | +优点:节省内存;风险:修改共享节点会影响两条链表。 |
| 210 | + |
| 211 | +## 第九章 闭包 |
| 212 | + |
| 213 | +**makeOnce** |
| 214 | + |
| 215 | +```javascript |
| 216 | +function makeOnce(fn) { |
| 217 | + let called = false; |
| 218 | + return function (...args) { |
| 219 | + if (called) return; |
| 220 | + called = true; |
| 221 | + return fn(...args); |
| 222 | + }; |
| 223 | +} |
| 224 | +``` |
| 225 | + |
| 226 | +**makeTimer** |
| 227 | + |
| 228 | +```javascript |
| 229 | +function makeTimer() { |
| 230 | + const start = Date.now(); |
| 231 | + return () => Date.now() - start; |
| 232 | +} |
| 233 | +``` |
| 234 | + |
| 235 | +**outer()() 为什么能输出** |
| 236 | + |
| 237 | +因为返回的函数闭包捕获了 `msg`,即使 `outer` 结束了,`msg` 仍然可访问。 |
| 238 | + |
| 239 | +## 第十章 对象 |
| 240 | + |
| 241 | +**Animal / Dog / Cat** |
| 242 | + |
| 243 | +```javascript |
| 244 | +class Animal { |
| 245 | + speak() { console.log('...'); } |
| 246 | +} |
| 247 | +class Dog extends Animal { |
| 248 | + speak() { console.log('woof'); } |
| 249 | +} |
| 250 | +class Cat extends Animal { |
| 251 | + speak() { console.log('meow'); } |
| 252 | +} |
| 253 | +``` |
| 254 | + |
| 255 | +**feed** |
| 256 | + |
| 257 | +```javascript |
| 258 | +function feed(animal) { |
| 259 | + animal.eat(); |
| 260 | +} |
| 261 | +``` |
| 262 | + |
| 263 | +**组合 move + draw** |
| 264 | + |
| 265 | +```javascript |
| 266 | +let canMove = { move() { console.log('move'); } }; |
| 267 | +let canDraw = { draw() { console.log('draw'); } }; |
| 268 | +let sprite = Object.assign({}, canMove, canDraw); |
| 269 | +``` |
| 270 | + |
| 271 | +## 第十一章 并发 |
| 272 | + |
| 273 | +**修复银行竞态(队列化)** |
| 274 | + |
| 275 | +```javascript |
| 276 | +function createQueue() { |
| 277 | + let last = Promise.resolve(); |
| 278 | + return function enqueue(task) { |
| 279 | + last = last.then(() => task()).catch(() => {}); |
| 280 | + return last; |
| 281 | + }; |
| 282 | +} |
| 283 | + |
| 284 | +let enqueue = createQueue(); |
| 285 | + |
| 286 | +enqueue(() => deposit(200)); |
| 287 | +enqueue(() => withdraw(200)); |
| 288 | +``` |
| 289 | + |
| 290 | +**顺序打印 1/2/3** |
| 291 | + |
| 292 | +```javascript |
| 293 | +function delay(ms) { |
| 294 | + return new Promise(resolve => setTimeout(resolve, ms)); |
| 295 | +} |
| 296 | + |
| 297 | +(async function () { |
| 298 | + for (let i = 1; i <= 3; i++) { |
| 299 | + await delay(1000); |
| 300 | + console.log(i); |
| 301 | + } |
| 302 | +})(); |
| 303 | +``` |
| 304 | + |
| 305 | +**任务队列(提示)** |
| 306 | + |
| 307 | +把每个任务包装成返回 Promise 的函数,然后用一个“串行链”依次执行。 |
0 commit comments