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

第 30 节 · SECTION 30

练习

知识巩固

数组和链表怎样找到元素

数组和单向链表中都按顺序保存了 [A, B, C, D, E]。现在要读取第 4 个元素 D:

  1. 数组可以直接使用哪个索引?
  2. 单向链表从头节点 A 开始,要沿着 next 依次经过哪些节点?
  3. 当要读取的元素位置越来越靠后时,两种结构所需的步骤会怎样变化?哪一种更适合反复按位置读取?为什么?

参考答案

  1. 若索引从 0 开始,第 4 个元素的索引是 3,数组可直接读取 arr[3]。

  2. 单向链表必须从头开始,访问路径为 A → B → C → D,需要沿 next 前进 3 次。

  3. 数组可以根据首地址和索引直接定位元素,按位置访问的时间复杂度为 $O(1)$。 单向链表访问第 $k$ 个节点时,必须从头节点开始,沿着 next 前进 $k-1$ 次, 最坏需要 $O(n)$ 时间。

    这里只比较按位置读取,不表示链表在所有操作上都更慢。

数组和链表怎样插入元素

数组和单向链表中都保存了 A、B、C、D。现在要在 B 后面插入 X:

  • 数组容量为 5,当前状态是 [A, B, C, D, _];
  • 链表为 A → B → C → D,并且已经拿到了指向节点 B 的引用。
  1. 数组需要移动哪些元素?写出插入后的数组。
  2. 链表应按什么顺序修改 X.next 和 B.next?写出插入后的链表。
  3. 为什么比较插入效率时,要特别说明“已经拿到了节点 B 的引用”?

参考答案

  1. 数组要先把 D 向右移动一格,再把 C 向右移动一格,最后把 X 放入索引 2, 得到 [A, B, X, C, D]。

  2. B.next 原本指向 C。应先令 X.next = B.next,让 X 指向 C; 再令 B.next = X。结果为 A → B → X → C → D。 如果先覆盖 B.next 又没有保存原来的连接,就可能找不到 C。

  3. 已知 B 的位置后,链表插入只需修改两个连接,可以在 $O(1)$ 时间完成。 如果还要从头查找 B,查找过程本身可能需要 $O(n)$ 时间。

列表容量是怎样增长的

一个基于数组实现的列表当前内容为 [A, B, C],长度 size = 3,容量 capacity = 4。 规定容量不足时,新数组的容量扩大为原来的 2 倍。

  1. 追加 D 后,列表的长度和容量分别是多少?是否需要扩容?
  2. 接着追加 E 时,容量会变为多少?需要复制几个原有元素?
  3. 底层数组的长度不可变,为什么列表的容量看起来却可以增长?

参考答案

  1. D 可以放入最后一个空位。此时内容为 [A, B, C, D], size = 4、capacity = 4,不需要扩容。

  2. 再追加 E 时已经没有空位,需要创建容量为 8 的新数组, 将 A、B、C、D 共 4 个原有元素复制过去,再加入 E。 此时 size = 5、capacity = 8。

  3. 原来的数组本身没有变长。列表创建了一个更大的新数组,复制原有元素, 再改用新数组作为底层存储,因此对使用者来说容量增长了。

编程练习

数组表示的大整数加一

数组 digits 从左到右保存一个非负整数的各位数字,例如 [3, 0, 8] 表示 308。 数字 0 用 [0] 表示;其他输入的第一位均不为 0。

请模拟一次十进制竖式加法,将这个整数增加 1,并把结果仍按相同的数组形式返回。 可以直接修改 digits;若最前面产生新的进位,可以返回一个更长的数组。

解题提示

  1. 像做竖式加法一样,从数组最后一位开始
  2. 当前位小于 9 时加一后即可立即返回
  3. 当前位等于 9 时把它改成 0;若所有位都是 9,需要在最前面补 1

LeetCode

反转单向链表

给定一个单向链表的头节点 head。每个节点包含一个值和指向下一节点的 next。

请使用迭代方法反转所有节点之间的连接,并返回反转后的头节点。 要求不创建新的链表节点。

解题提示

  1. 先在纸上画出三个相连的节点和 prev、cur 两个指针
  2. 改写 cur.next 前,必须先用 nxt 保存原来的下一个节点
  3. 反转 cur.next 后,令 prev = cur,再令 cur = nxt,继续处理原链表中的下一个节点

LeetCode 题目解析