136 - 只出现一次的数字
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
说明:你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
用数组/集合
class Solution:
def singleNumber(self, nums: List[int]) -> int:
tmp = []
for n in nums:
if n in tmp:
tmp.remove(n)
else:
tmp.append(n)
return tmp[0]
用tmp记录已访问但未被配对的nums中的元素,题目说只有一个元素出现一次,那么当nums遍历完成,tmp中剩下的就是只出现一次的元素。
注意python列表的remove方法可以实现直接移除元素,而pop只能按照移除索引移除。当列表中有多个相同元素时,remove方法移除首个。
位异或
class Solution:
def singleNumber(self, nums: List[int]) -> int:
crit = 0
for n in nums:
crit ^= n
return crit
位异或我是第一次听说,不过这法子也太秀了。
异或是指:如果a、b两个值不相同,则异或结果为1。如果a、b两个值相同,异或结果为0。
以下来自:通过哈希集、按位异或操作符解决(解题思路)
举一例例子:
甲:0 0 0 0 1 1 0 0 (值为12)
乙:0 0 0 0 0 1 1 1 (值为7)
甲和乙进行按位异或操作得到新值丙
丙:0 0 0 0 1 0 1 1 (值为11)
可见,当两值的某一位的值不同时,按位异或操作后所得新值某一位的值将为 1 (如从右到左第一位),反之为 0 (如从右到左第三位)。
通过观察,和结合按位异或操作符的性质我们可以发现一个按位异或操作的性质:一个值和0进行按位异或操作所得为该值,相同的两个值进行异或操作,所得为0(甲 按位异或 0 得 甲,甲 按位异或 甲 得 0)。根据这个性质,由于每个重复元素重复两次,故他们在遍历后将相互抵消,而唯一元素只出现一次,故将得到保留。数组 12、7、12 之所以依次按位异或结果为 7 是因为按位异或操作符满足交换律。即:
12 xor 7 xor 12
= 12 xor 12 xor 7
= 0 xor 7
= 7
python自带的异或运算^
,可以分为三步:
- 把两个十进制整数转换成二进制,并让其长度相同(短的前面补0);
- 将两个二进制数按位异或(相同时为1,不同时为0);
- 将结果转换回十进制数,输出。
141 - 环形链表
给定一个链表,判断链表中是否有环。
class Solution:
def hasCycle(self, head: ListNode) -> bool:
hasVisit = []
while head is not None:
if head in hasVisit:
return True
else:
hasVisit.append(head)
head = head.next
return False
这是一版简单解法,只需遍历链表,然后看看已遍历的节点是否重复出现即可。
class Solution:
def hasCycle(self, head: ListNode) -> bool:
i1,i2 = head,head
while i1 is not None and i2 is not None:
i1 = i1.next
i2 = i2.next
if i2 is not None: #不成环时,避免出现i2.next为None,则i2.next.next报错
i2 = i2.next
else:
break
if i1 == i2:
return True
return False
这一个版本想用追击法,具体为创建两个指针,一个一次走一步,另一个一次走两步。如果成环,那么经过若干次,两个指针一定会遇上。优点是可以计算成环的节点数目,因为两指针第一次相遇时所走的距离差正好是环的长度。
142 - 环形链表 II
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意,pos 仅仅是用于标识环的情况,并不会作为参数传递到函数中。说明:不允许修改给定的链表。
进阶:你是否可以使用 O(1) 空间解决此题?
class Solution:
def detectCycle(self, head: ListNode) -> ListNode:
fast, slow = head, head
while fast:
slow = slow.next
fast = fast.next
if not fast:
return None
else:
fast = fast.next
if slow == fast:
break
if not fast:
return None
fast = head
while slow != fast:
slow = slow.next
fast = fast.next
return slow
这道题目比之前的环形链表的题目更难一些,还是利用双指针,具体可参考双指针技巧直接秒杀五道算法题
第一次相遇时,假设慢指针slow走了k步,那么快指针fast一定走了2k步。fast一定比slow多走了k步,这多走的k步其实就是fast指针在环里转圈圈,所以k的值就是环长度的整数倍。
设相遇点距环的起点的距离为m,那么环的起点距头结点head的距离为k - m,也就是说如果从head前进k - m步就能到达环起点。巧的是,如果从相遇点继续前进k - m步,也恰好到达环起点。你甭管fast在环里到底转了几圈,反正走k步可以到相遇点,那走 k - m 步一定就是走到环起点了。所以,只要我们把快慢指针中的任一个重新指向head,然后两个指针同速前进,k - m步后就会相遇,相遇之处就是环的起点了。
- 为什么是环长度整数倍?
我举个简单的例子你就明白了,我们想一想极端情况,假设环长度就是 1,那么fast肯定早早就进环里转圈圈了,而且肯定会转好多圈,这不就是环长度的整数倍嘛。