今天学习一下链表,选的题目是leetcode的剑指offer06.从尾到头打印链表。先看下链表的定义:是一种非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现。今天要用到的只是单链表,所以链表中只有next指针;而双链表,则会有两个指针:next和prev。
了解完定义,来看下题目:
输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。
示例:
输入:head = [1,3,2]
输出:[2,3,1]
示例中只是用一个数组来表示链表,方便表达题意;在方法中传入的参数会是一个完整的链表(代码中的ListNode就是一个单独的节点)。关于这题,其实没什么技巧性,因为要拿到链表的最后一个值,就必须通过遍历整个链表找到最后节点。下面给出三个解决方案。
第一种:从头遍历整个链表,每找到一个节点,就插入到数组前面去。不过这种解决方案时间复杂度会比较高,因为数组结构的原因,在文章末尾再进行说明。
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
var reversePrint = function(head) {
if (!head) return[]
let res = []
dfs(head)
function dfs(head) {
res.unshift(head.val)
if (head.next) dfs(head.next)
}
return res
};
第二种,可以把上面的数组换成字符串来存储,不过实现方式有点龊,每次遍历就把值拼到字符串前面,需要加上一个标识符。直到遍历完再把字符串转成数组。
var reversePrint = function(head) {
if (!head) return[]
let str = ''
dfs(head)
function dfs(head) {
str = str ? head.val + "@" + str : String(head.val)
if (head.next) dfs(head.next)
}
return str.split('@')
};
第三种,递归。递归会一层一层进去,到达里面的界限,又会一层一层地回来,把每层的值给插到数组的后面,时间复杂度会比前面的低。
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
var reversePrint = function(head) {
if (!head) return[]
let arr = reversePrint(head.next)
arr.push(head.val)
return arr
};
这题主要要考虑的是遍历整个链表后怎样取值的问题。下面来说下为什么往数组前面插入新值的时间复杂度会比较高,因为数组在内存上是一段连续的空间。如果要在前面或中间插入新值,则需要把后面元素都往后挪一位,把前面位置空出来才能插入新值;如果是在后面插入新值,则不需要挪位置,可以直接插入新值。
而链表在前面新增元素则方便很多,直接把指针修改一下就好了。不过在查找某个下标元素方面,链表的时间复杂度较高,因为只能根据指针从头找到尾;而数组则可以直接通过下标访问。
不同的数组结构有不同的特点,需要根据实际需求选择最适合或较适合的数据结构