4.6 练习¶
4.6.1 知识巩固¶
1. 数组和链表怎样找到元素¶
数组和单向链表中都按顺序保存了 [A, B, C, D, E]。现在要读取第 4 个元素 D:
- 数组可以直接使用哪个索引?
- 单向链表从头节点
A开始,要沿着next依次经过哪些节点? - 当要读取的元素位置越来越靠后时,两种结构所需的步骤会怎样变化?哪一种更适合反复按位置读取?为什么?
参考答案
-
若索引从 0 开始,第 4 个元素的索引是 3,数组可直接读取
arr[3]。 -
单向链表必须从头开始,访问路径为
A → B → C → D,需要沿next前进 3 次。 -
数组可以根据首地址和索引直接定位元素,按位置访问的时间复杂度为 \(O(1)\)。 单向链表访问第 \(k\) 个节点时,必须从头节点开始,沿着
next前进 \(k-1\) 次, 最坏需要 \(O(n)\) 时间。这里只比较按位置读取,不表示链表在所有操作上都更慢。
2. 数组和链表怎样插入元素¶
数组和单向链表中都保存了 A、B、C、D。现在要在 B 后面插入 X:
- 数组容量为 5,当前状态是
[A, B, C, D, _]; - 链表为
A → B → C → D,并且已经拿到了指向节点B的引用。
- 数组需要移动哪些元素?写出插入后的数组。
- 链表应按什么顺序修改
X.next和B.next?写出插入后的链表。 - 为什么比较插入效率时,要特别说明“已经拿到了节点 B 的引用”?
参考答案
-
数组要先把
D向右移动一格,再把C向右移动一格,最后把X放入索引 2, 得到[A, B, X, C, D]。 -
B.next原本指向C。应先令X.next = B.next,让X指向C; 再令B.next = X。结果为A → B → X → C → D。 如果先覆盖B.next又没有保存原来的连接,就可能找不到C。 -
已知
B的位置后,链表插入只需修改两个连接,可以在 \(O(1)\) 时间完成。 如果还要从头查找B,查找过程本身可能需要 \(O(n)\) 时间。
3. 列表容量是怎样增长的¶
一个基于数组实现的列表当前内容为 [A, B, C],长度 size = 3,容量 capacity = 4。
规定容量不足时,新数组的容量扩大为原来的 2 倍。
- 追加
D后,列表的长度和容量分别是多少?是否需要扩容? - 接着追加
E时,容量会变为多少?需要复制几个原有元素? - 底层数组的长度不可变,为什么列表的容量看起来却可以增长?
参考答案
-
D可以放入最后一个空位。此时内容为[A, B, C, D],size = 4、capacity = 4,不需要扩容。 -
再追加
E时已经没有空位,需要创建容量为 8 的新数组, 将A、B、C、D共 4 个原有元素复制过去,再加入E。 此时size = 5、capacity = 8。 -
原来的数组本身没有变长。列表创建了一个更大的新数组,复制原有元素, 再改用新数组作为底层存储,因此对使用者来说容量增长了。
4.6.2 编程练习¶
1. 数组表示的大整数加一¶
数组 digits 从左到右保存一个非负整数的各位数字,例如 [3, 0, 8] 表示 308。
数字 0 用 [0] 表示;其他输入的第一位均不为 0。
请模拟一次十进制竖式加法,将这个整数增加 1,并把结果仍按相同的数组形式返回。
可以直接修改 digits;若最前面产生新的进位,可以返回一个更长的数组。
解题提示
- 像做竖式加法一样,从数组最后一位开始
- 当前位小于 9 时加一后即可立即返回
- 当前位等于 9 时把它改成 0;若所有位都是 9,需要在最前面补 1
2. 反转单向链表¶
给定一个单向链表的头节点 head。每个节点包含一个值和指向下一节点的 next。
请使用迭代方法反转所有节点之间的连接,并返回反转后的头节点。 要求不创建新的链表节点。
解题提示
- 先在纸上画出三个相连的节点和 prev、cur 两个指针
- 改写 cur.next 前,必须先用 nxt 保存原来的下一个节点
- 反转 cur.next 后,令 prev = cur,再令 cur = nxt,继续处理原链表中的下一个节点