返回首页返回书籍《Hello 算法》
目录

目录 · CONTENTS

  1. 01序
  2. 02前言
  3. 03关于本书
  4. 04如何使用本书
  5. 05小结
  6. 06初识算法
  7. 07算法无处不在
  8. 08算法是什么
  9. 09小结
  10. 10复杂度分析
  11. 11算法效率评估
  12. 12迭代与递归
  13. 13时间复杂度
  14. 14空间复杂度
  15. 15小结
  16. 16练习
  17. 17数据结构
  18. 18数据结构分类
  19. 19基本数据类型
  20. 20数字编码
  21. 21字符编码
  22. 22小结
  23. 23练习
  24. 24数组与链表
  25. 25数组
  26. 26链表
  27. 27列表
  28. 28内存与缓存
  29. 29小结
  30. 30练习
  31. 31栈与队列
  32. 32栈
  33. 33队列
  34. 34双向队列
  35. 35小结
  36. 36练习
  37. 37哈希表
  38. 38哈希表
  39. 39哈希冲突
  40. 40哈希算法
  41. 41小结
  42. 42练习
  43. 43树
  44. 44二叉树
  45. 45二叉树遍历
  46. 46二叉树数组表示
  47. 47二叉搜索树
  48. 48AVL 树
  49. 49小结
  50. 50练习
  51. 51堆
  52. 52堆
  53. 53建堆操作
  54. 54Top-k 问题
  55. 55小结
  56. 56练习
  57. 57图
  58. 58图
  59. 59图的基础操作
  60. 60图的遍历
  61. 61小结
  62. 62练习
  63. 63搜索
  64. 64二分查找
  65. 65二分查找插入点
  66. 66二分查找边界
  67. 67哈希优化策略
  68. 68重识搜索算法
  69. 69小结
  70. 70练习
  71. 71排序
  72. 72排序算法
  73. 73选择排序
  74. 74冒泡排序
  75. 75插入排序
  76. 76快速排序
  77. 77归并排序
  78. 78堆排序
  79. 79桶排序
  80. 80计数排序
  81. 81基数排序
  82. 82小结
  83. 83练习
  84. 84分治
  85. 85分治算法
  86. 86分治搜索策略
  87. 87构建二叉树问题
  88. 88汉诺塔问题
  89. 89小结
  90. 90练习
  91. 91回溯
  92. 92回溯算法
  93. 93全排列问题
  94. 94子集和问题
  95. 95n 皇后问题
  96. 96小结
  97. 97练习
  98. 98动态规划
  99. 99初探动态规划
  100. 100动态规划问题特性
  101. 101动态规划解题思路
  102. 1020-1 背包问题
  103. 103完全背包问题
  104. 104编辑距离问题
  105. 105小结
  106. 106练习
  107. 107贪心
  108. 108贪心算法
  109. 109分数背包问题
  110. 110最大容量问题
  111. 111最大切分乘积问题
  112. 112小结
  113. 113练习
  114. 114附录
  115. 115编程环境安装
  116. 116一起参与创作
  117. 117术语表
  118. 118参考文献
  119. 119纸质书
第 90 / 119 节

第 90 节 · SECTION 90

练习

知识巩固

哪些任务适合分治

一位同学想用“先分成两半,分别解决,再合并结果”的方法完成以下任务。 请分别判断它们是“适合分治”“可以分治,但不会减少总工作量”还是“两半不能独立解决”,并说明理由。

  1. 将一个无序数组排序;
  2. 求一个数组中的最大值;
  3. 按顺序执行一串 push(x)、pop() 栈操作,并输出每次 pop() 得到的元素。

参考答案

  1. 适合:对半分解、两半独立排序、$O(n)$ 合并——这正是归并排序。
  2. 可以分治,但不会减少总工作量:左右两半仍需合计检查全部 $n$ 个元素, 和直接扫描一样都是 $O(n)$ 时间。
  3. 两半不能独立解决:后半段开始时的栈内容取决于前半段执行结果, 两半不能在互不知道结果时独立完成。

快速幂是怎样减少计算的

下面的递归函数用分治计算 $x^n$:

[file]{fast_power}-[class]{}-[func]{fast_pow}

令 x = 3、n = 5,用这个函数计算:

  1. 递归调用时,参数 n 依次变成哪些值?
  2. 从最深层开始返回时,各层依次返回什么值?
  3. 为什么要先把递归结果保存为 half,而不是在乘法两边各调用一次相同的子问题?

??? success "参考答案"

1. 参数依次为 `5 → 2 → 1 → 0`,每次都把指数减半,直到到达终止条件。

2. `n = 0` 时返回 1;`n = 1` 时返回 $1×1×3=3$;
    `n = 2` 时返回 $3×3=9$;`n = 5` 时返回 $9×9×3=243$。

3. 如果在乘法两边各调用一次相同的子问题,两次递归会进行完全相同的计算。
    先把结果保存为 `half`,每层就只递归一次,递归深度约为 $\log n$;
    调用两次会造成大量重复计算。

从遍历序列拆分左右子树

一棵没有重复节点的二叉树,其前序遍历和中序遍历分别为:

  • 前序遍历:[A, B, D, E, C]
  • 中序遍历:[D, B, E, A, C]

只完成根节点这一层的拆分,不必继续递归,也不必画出整棵树:

  1. 根节点是什么?
  2. 左、右子树在中序遍历中分别对应哪一段?
  3. 左、右子树在前序遍历中分别对应哪一段?根节点有哪些直接孩子?

??? success "参考答案"

1. 前序遍历的第一个节点是根节点,因此根节点为 `A`。

2. `A` 把中序遍历分成两部分:左子树为 `[D, B, E]`,右子树为 `[C]`。

3. 左子树有 3 个节点,因此根节点 `A` 后面的 3 个前序元素属于左子树,
    即 `[B, D, E]`;剩余的 `[C]` 属于右子树。
    所以根节点的左孩子是 `B`,右孩子是 `C`。

编程练习

快速幂

给定实数 x 和整数 n,请计算 $x^n$,且不能调用语言内置的幂函数。 要求使用递归分治:每次把指数缩小一半,并复用已经算出的子问题结果。 本题规定 $x^0=1$,包括 x = 0 时;当 n < 0 时保证 x != 0,答案可转化为 $(1/x)^{-n}$。

??? tip "解题提示"

1. n 等于 0 时答案是 1
2. 算出 x 的 n // 2 次方后保存为 half,不要递归调用第二次
3. 当 n < 0 时,先把 x 改为 1 / x,再把 n 改为 -n;使用 C++ 或 Java 时,可先把 n 转为 64 位整数,避免最小的 32 位整数取相反数时溢出

LeetCode{ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" } 题目解析{ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" }

说明: 链接中的题解使用迭代位运算,与本练习指定的递归分治方法不同;请根据本题的解题提示完成递归版本