「这是我参与11月更文挑战的第一天,活动详情查看:2021最后一次更文挑战」
各位小伙伴大家好啊,今天是第二天了。由于第一天题解没写完,所以和今天的题解一起发布出来。
第一题 剑指 Offer 06. 从尾到头打印链表
输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。
示例 1:
输入: head = [1,3,2]
输出: [2,3,1]
限制:
0 <= 链表长度 <= 10000
解题思路:
看到这个题目,首先想到数组的unshift方法,它能够在数组的前面插入元素。于是有了解法一。
解法一
遍历链表,将每个元素插入到数组头部,返回数组。
看了官方解答后,发现官方利用了栈先进后出的特点。在我看来,该方法没有解法一好,因为相当于遍历了两次链表(入栈一次,出栈一次),而解法一仅遍历一次。此方法还增加了额外的辅助栈。但最后本着练习的想法,还是实现了一下。
代码实现:
// 解法一
var reversePrint = function(head) {
let ans = [];
let node = head;
while(node) {
ans.unshift(node.val);
node = node.next;
}
return ans;
};
// 解法二
var reversePrint = function(head) {
let stack = [];
let node = head;
while(node) {
stack.push(node.val);
node = node.next;
}
let arr = [];
while(stack.length > 0) {
arr.push(stack.pop());
}
return arr;
};
注意:head指针的val含有值。
第二题 剑指 Offer 24. 反转链表
定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
限制:
0 <= 节点个数 <= 5000
解题思路:我们需要将1 -> 2 -> 3 -> null,变为null <- 1 <- 2 <- 3。不难想到利用两个指针,pre指针指向前面的节点,now指针指向当前的节点,然后将now节点的next指向pre节点,即可完成链表的反转。
第二种解法:该解法主要是最近接触栈的数据结构比较多,由于栈的特点——先进后出,所以用来反转数据是十分合适的。将链表所有节点入栈,再将所有节点出栈(同时更改其指向),该链表就已经被反转了。
代码实现:
// 解法一
var reverseList = function(head) {
let pre = null;
let now = head;
while(now) {
let next = now.next;
now.next = pre;
pre = now;
now = next;
}
return pre;
};
// 解法二
var reverseList = function(head) {
// 如果链表为空,不用反转
if(head === null) {
return null;
}
let stack = [];
let node = head;
while(node) {
stack.push(node);
node = node.next;
}
let nowHead = stack.pop();
let p = nowHead;
while(stack.length > 0) {
let nowNode = stack.pop();
p.next = nowNode;
p = p.next;
}
p.next = null;
return nowHead;
};
注意:1、head指针的val含有值。2、在更改当前节点的next指向的时候,需要先进行存储,防止找不到原链表下一个节点。
第三题 剑指 Offer 35. 复杂链表的复制
请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。
示例 1:
输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]
示例 2:
输入:head = [[1,1],[2,1]]
输出:[[1,1],[2,1]]
示例 3:
输入:head = [[3,null],[3,0],[3,null]]
输出:[[3,null],[3,0],[3,null]]
示例 4:
输入:head = []
输出:[]
解释:给定的链表为空(空指针),因此返回 null。
提示:
-10000 <= Node.val <= 10000Node.random 为空(null)或指向链表中的节点。节点数目不超过 1000 。
来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/fu…
解题思路:copy普通链表仅需要遍历该链表一次,但此题中的链表有点不同,它含有一个random的指针指向随机值。思考一会儿后,觉得先复制普通链表,然后再去复制random指针,但是在第二次遍历复制random指针的时候会发现copy的链表无法找到那个random指向的节点。因此看了题解,发现可以使用Map结构去构造一个原链表地址和copy链表节点的映射。如此一来,就可以通过原链表的地址,找到copy链表的每个节点,就可以找到random节点了。所以出现解法一(哈希) 。
最后题解还给出了一种解法:原地复制。先将每个节点复制一份放在该节点后面,那么就可以通过node.next.random = node.random来给copy链表节点的random赋值了,最后会得到一个两个链表连接体,那么我们只需要将每一个节点的next指向next.next便将原链表和copy链表拆分开了。
代码实现:
// 解法一(哈希)
var copyRandomList = function(head) {
let node = head;
let map = new Map();
// 创建原地址和copy链表节点的映射
while(node) {
map.set(node, new Node(node.val));
node = node.next;
}
// 处理random指向
node = head;
while(node) {
let now = map.get(node);
// 处理边界值
now.next = map.get(node.next) === undefined ? null : map.get(node.next);
now.random = map.get(node.random) === undefined ? null : map.get(node.random);
node = node.next;
}
return map.get(head);
};
// 解法二(原地复制)
var copyRandomList = function(head) {
// 不用复制
if(head === null) {
return null;
}
// 在每一个节点后面复制自身
let node = head;
while(node) {
let copyNode = new Node(node.val);
copyNode.next = node.next;
node.next = copyNode;
node = node.next.next;
}
// 将节点后的复制节点random指向更改
node = head;
while(node) {
// 因为 node.random.next 是 node.random 的复制
node.next.random = node.random === null ? null : node.random.next;
node = node.next.next;
}
// 将复制好的链表和原链表分离
node = head;
const newHead = head.next;
while(node.next) {
let temp = node.next;
node.next = temp.next;
node = temp;
}
return newHead;
};
注意:作者在写该题目的时候,没有考虑到边界情况。1、如果原链表为null,next将会报错,则放在开始进行处理。2、如果是random指向最后的null,那么node.random.next也会报错。
总结
今天前两题难度不大,关键为第三题。当碰到第三题的时候,没有想出好的点子,可能是因为刷的题还不够多。接下来,我会继续努力,慢慢进步的。如果有什么不对的地方,或者有更好的方案,欢迎提出,大家一起讨论。