返回首页返回书籍《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纸质书
第 16 / 119 节

第 16 节 · SECTION 16

练习

知识巩固

迭代与递归的时间和空间

下面两段代码都计算 $1 + 2 + \dots + n$(设 $n \ge 1$)。请把 n 设为 4, 按照程序实际执行的顺序回答问题,然后比较两种写法的效率。

[file]{complexity_exercises}-[class]{}-[func]{sum_iter}
  1. 输入 n = 4 执行迭代函数时,每轮循环结束后,累加变量 res 的值分别是多少?
  2. 输入 n = 4 执行递归函数时,参数 n 会依次取哪些值?从最深的一层开始返回时,结果怎样得到?
  3. 两种写法的时间复杂度和空间复杂度分别是多少?结合第 1、2 问的执行过程说明理由。

??? success "参考答案"

1. 循环变量 `i` 依次为 `1、2、3、4`,每轮结束后,`res` 依次变为
    `1、3、6、10`,所以迭代函数返回 10。

2. 参数 `n` 依次为 `4 → 3 → 2 → 1`。
    最深一层返回 1,随后各层依次得到 `2 + 1 = 3`、`3 + 3 = 6`、`4 + 6 = 10`。
    在最深处,4 次函数调用都尚未结束。

3. 两段代码都进行与 $n$ 成正比的循环或调用,因此时间复杂度均为 $O(n)$ 。
    空间复杂度不同:迭代版只使用常数个变量,为 $O(1)$ ;
    递归版在到达终止条件前,前面的函数调用都要等待返回结果,因此调用栈中最多同时保存 $n$ 次调用,
    空间复杂度为 $O(n)$。

    分析空间复杂度时,除代码中的变量外,还要考虑递归调用占用的空间。

三段代码的时间复杂度

以下三个代码片段的输入均为正整数 $n$ 。请按时间复杂度从低到高排序,并写出各自的复杂度。

[file]{complexity_exercises}-[class]{}-[func]{linear_loop}

??? success "参考答案"

从低到高为:片段三 $O(\log n)$、片段一 $O(n)$、片段二 $O(n^2)$。
片段三每轮把 $n$ 缩小为原来的一半,约循环 $\log_2 n$ 次。
片段一的循环恰好执行 $n$ 次。片段二的内层循环次数依次为
$n,n-1,\dots,1$,总次数为 $n(n+1)/2$,因此属于平方阶。

哪种反转更节省空间

要将数组 nums 中的元素全部反转,有两种做法:

  1. 新建一个等长数组 res,倒序复制后返回;

  2. 用两个索引 i 和 j 分别从首、尾向中间移动,逐对交换 nums[i] 与 nums[j] 。

    两种做法的空间复杂度各是多少?哪种属于“原地”操作?

??? success "参考答案"

1. 需要与输入等长的辅助数组,空间复杂度 $O(n)$。

2. 只使用两个索引变量,
    空间复杂度 $O(1)$ ,属于原地操作。

    需要注意:原地反转会修改输入数组,
    仅在允许修改输入时才应优先选用;若需保留原数组,第 1 种做法的复制开销不可避免。

编程练习

斐波那契数

斐波那契数列满足:$F(0)=0$、$F(1)=1$,并且当 $n\ge2$ 时, $F(n)=F(n-1)+F(n-2)$。

给定非负整数 n,请使用循环计算并返回 $F(n)$,不使用递归。

??? tip "解题提示"

1. 先单独处理 n 为 0 和 1 的情况
2. 计算下一项时只需要前两项,无须保存整个数列
3. 更新两个变量时,注意不要过早覆盖仍会用到的旧值

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