TLP-Task11学习笔记


本篇为Datawhale组队学习计划第21期LeetCode精选题目组Task11学习笔记。
初学,时间有点仓促,很多解法没有详细分析,未来可能会修改,见谅。
Datawhale学习文档:
https://github.com/datawhalechina/team-learning-program/tree/master/LeetCodeTencent

136 只出现一次的数字

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/single-number

给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?

示例:

输入: [2,2,1]
输出: 1
输入: [4,1,2,1,2]
输出: 4

思路

官方题解:如果没有时间复杂度和空间复杂度的限制,这道题有很多种解法,可能的解法有如下几种。

  • 使用集合存储数字。遍历数组中的每个数字,如果集合中没有该数字,则将该数字加入集合,如果集合中已经有该数字,则将该数字从集合中删除,最后剩下的数字就是只出现一次的数字。
  • 使用哈希表存储每个数字和该数字出现的次数。遍历数组即可得到每个数字出现的次数,并更新哈希表,最后遍历哈希表,得到只出现一次的数字。
  • 使用集合存储数组中出现的所有数字,并计算数组中的元素之和。由于集合保证元素无重复,因此计算集合中的所有元素之和的两倍,即为每个元素出现两次的情况下的元素之和。由于数组中只有一个元素出现一次,其余元素都出现两次,因此用集合中的元素之和的两倍减去数组中的元素之和,剩下的数就是数组中只出现一次的数字。

需要额外使用O(n) 的空间,为了满足线性时间复杂度,官解的方法是位运算。异或的性质:

  • 任何数和0做异或运算,结果仍然是原来的数,即 a⊕0=a
  • 任何数和其自身做异或运算,结果是0,即 a⊕a=0
  • 异或运算满足交换律和结合律,即
    a⊕b⊕a=b⊕a⊕a=b⊕(a⊕a)=b

对数组中全部元素进行异或运算,交换律和结合律可以使我们随意移动数字位置,而不改变异或的运算结果。
a1⊕a2⊕a2⊕a3⊕b⊕a3⊕a1 =(a1⊕a1)⊕(a2⊕a2)⊕(a3⊕a3)⊕b
=b
最终,对任何出现了两次(或偶数次)的数都可以转换成a⊕a=0,出现一次(或奇数次,利用2n+1消去)的数则通过b⊕0=b保留下来。
本题中只有出现了一次或两次的情况,所以数组中的全部元素的异或运算结果即为数组中只出现一次的数字。

Python实现

(官解的一行代码)
其中异或运算符为^

class Solution:
    def singleNumber(self, nums: List[int]) -> int:
        return reduce(lambda x, y: x ^ y, nums)

141 环形链表

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/linked-list-cycle

给定一个链表,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。**注意:pos 不作为参数进行传递,**仅仅是为了标识链表的实际情况。
如果链表中存在环,则返回 true 。 否则,返回 false 。

进阶:

你能用 O(1)(即,常量)内存解决此问题吗?

示例 :

输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。

输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。
在这里插入图片描述

输入:head = [1], pos = -1
输出:false
解释:链表中没有环。

提示:

链表中节点的数目范围是 [0, 10^4]
-105 <= Node.val <= 105
pos 为 -1 或者链表中的一个 有效索引

思路

双指针法。官解中的解释:

假想「乌龟」和「兔子」在链表上移动,「兔子」跑得快,「乌龟」跑得慢。当「乌龟」和「兔子」从链表上的同一个节点开始移动时,如果该链表中没有环,那么「兔子」将一直处于「乌龟」的前方;如果该链表中有环,那么「兔子」会先于「乌龟」进入环,并且一直在环内移动。等到「乌龟」进入环时,由于「兔子」的速度快,它一定会在某个时刻与乌龟相遇,即套了「乌龟」若干圈。

定义两个指针,一快一满。慢指针每次只移动一步,而快指针每次移动两步。初始时,慢指针在位置 head,而快指针在位置 head.next。这样一来,如果在移动的过程中,快指针反过来追上慢指针,就说明该链表为环形链表。否则快指针将到达链表尾部,该链表不为环形链表

细节:用while循环时,要规定初始时慢指针在位置 head,快指针在位置 head.next

Python实现

class Solution:
    def hasCycle(self, head: ListNode) -> bool:
        if not head or not head.next:
            return False
        
        slow = head
        fast = head.next

        while slow != fast:
            if not fast or not fast.next:
                return False
            slow = slow.next
            fast = fast.next.next
        
        return True


#作者:LeetCode-Solution
#链接:https://leetcode-cn.com/problems/linked-list-cycle/solution/huan-xing-lian-biao-by-leetcode-solution/
#来源:力扣(LeetCode)
#著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

142 环形链表Ⅱ

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/linked-list-cycle-ii

给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意,pos 仅仅是用于标识环的情况,并不会作为参数传递到函数中。
说明:不允许修改给定的链表。

进阶:

你是否可以使用 O(1) 空间解决此题?

示例(同上一题):

输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。

输入:head = [1,2], pos = 0
输出:返回索引为 0 的链表节点
解释:链表中有一个环,其尾部连接到第一个节点。

输入:head = [1], pos = -1输出:返回 null
解释:链表中没有环。

思路

https://leetcode-cn.com/problems/linked-list-cycle-ii/solution/linked-list-cycle-ii-kuai-man-zhi-zhen-shuang-zhi-/

双指针第一次相遇: 设两指针 fast,slow 指向链表头部 head,fast 每轮走2步,slow 每轮走1步;

第一种结果: fast 指针走过链表末端,说明链表无环,直接返回 null;
TIPS: 若有环,两指针一定会相遇。因为每走 111 轮,fast 与 slow 的间距 +1+1+1,fast 终会追上 slow;

第二种结果: 当fast == slow时, 两指针在环中 第一次相遇 。下面分析此时fast 与 slow走过的 步数关系

  • 设链表共有a+b个节点,其中 链表头部到链表入口 有a个节点(不计链表入口节点), 链表环 有b个节点(这里需要注意,a和b是未知数;设两指针分别走了f,s步,则有:
    fast 走的步数是slow步数的2倍,即f=2s;( fast 每轮走2步)
    fast 比 slow多走了n个环的长度,即f=s+nb;( 解析: 双指针都走过a步,然后在环内绕圈直到重合,重合时 fast 比 slow 多走 环的长度整数倍 );
  • 以上两式相减得:f=2nb,s=nb,即fast和slow 指针分别走了 2n和n个 环的周长 (注意:n是未知数,不同链表的情况不同)。

目前情况分析:

如果让指针从链表头部一直向前走并统计步数k,那么所有 走到链表入口节点时的步数 是:k=a+nb(先走a步到入口节点,之后每绕1 圈环(b步)都会再次到入口节点)。
而目前,slow 指针走过的步数为nb步。因此,我们只要想办法让 slow 再走a步停下来,就可以到环的入口。
但是我们不知道a的值,该怎么办?依然是使用双指针法。我们构建一个指针,此指针需要有以下性质:此指针和slow 一起向前走 a 步后,两者在入口节点重合。那么从哪里走到入口节点需要a步?答案是链表头部head。

双指针第二次相遇:

slow指针位置不变,将fast指针重新 指向链表头部节点 ;slow和fast同时每轮向前走11步;
TIPS:此时f=0,s=nb;
当 fast 指针走到f=a步时,slow 指针走到步s=a+nb,此时 两指针重合,并同时指向链表环入口 。

返回slow指针指向的节点。

Python实现

class Solution(object):
    def detectCycle(self, head):
        fast, slow = head, head
        while True:
            if not (fast and fast.next): return
            fast, slow = fast.next.next, slow.next
            if fast == slow: break
        fast = head
        while fast != slow:
            fast, slow = fast.next, slow.next
        return fast


#作者:jyd
#链接:https://leetcode-cn.com/problems/linked-list-cycle-ii/solution/linked-list-cycle-ii-kuai-man-zhi-zhen-shuang-zhi-/
#来源:力扣(LeetCode)
#著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

猜你喜欢

转载自blog.csdn.net/cosima0/article/details/113012951