LeetCode 每日两题 第二天

149 阅读4分钟

「这是我参与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。

示例 1image.png

输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]

示例 2image.png

输入:head = [[1,1],[2,1]]
输出:[[1,1],[2,1]]

示例 3image.png

输入:head = [[3,null],[3,0],[3,null]]
输出:[[3,null],[3,0],[3,null]]

示例 4

输入:head = []
输出:[]
解释:给定的链表为空(空指针),因此返回 null。

提示

  • -10000 <= Node.val <= 10000
  • Node.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也会报错。

总结

今天前两题难度不大,关键为第三题。当碰到第三题的时候,没有想出好的点子,可能是因为刷的题还不够多。接下来,我会继续努力,慢慢进步的。如果有什么不对的地方,或者有更好的方案,欢迎提出,大家一起讨论。