这篇博客记录刷题第11天的学习心得与收获。
136. 只出现一次的数字
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
示例 1:
输入: [2,2,1] 输出: 1
示例 2:
输入: [4,1,2,1,2] 输出: 4
分析:这题真的是脑筋急转弯啊,不用额外空间,且应该具有线性时间复杂度 O ( n ) O(n) O(n),看了评论才知道原来是用异或来做!异或 ^ 操作有如下性质:
-
交换律:a ^ b ^ c <=> a ^ c ^ b
-
任何数与0异或为其本身 0 ^ n => n
-
相同的数异或为0: n ^ n => 0
看到这里,如果我们把nums数组中元素全都取出来做异或,那成对的元素异或之后都变成0了,0与剩余的单个元素异或就是其本身,就能找出那个只出现了一次的元素。代码如下:
class Solution {
public:
int singleNumber(vector<int>& nums) {
int a = 0;
for (int i = 0; i < nums.size(); i++) {
a = a ^ nums[i];
}
return a;
}
};
141.环形链表
给定一个链表,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意:pos不作为参数进行传递,仅仅是为了标识链表的实际情况。
如果链表中存在环,则返回 true 。 否则,返回 false 。
进阶:
你能用 O(1)(即,常量)内存解决此问题吗?
示例 1:
输入:head = [3,2,0,-4], pos = 1 输出:true 解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0 输出:true 解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1 输出:false 解释:链表中没有环。提示:
链表中节点的数目范围是 [0, 104] -105 <= Node.val <= 105 pos 为 -1 或者链表中的一个 有效索引 。
分析:这里直接摘抄题解区[1], 对于链表是否有环,定义两个指针fast和slow,初始时都指向头节点,每次移动时,fast向后走两次,slow向后走一次,直到 fast 无法向后走两次。这使得在每轮移动之后。fast 和 slow 的距离就会增加1,这就是快慢指针。
当一个链表有环时,快慢指针都会陷入环中进行无限次移动,然后变成了追及问题。想象一下在操场跑步的场景,只要一直跑下去,快的总会追上慢的。当两个指针都进入环后,每轮移动使得慢指针到快指针的距离增加一,同时快指针到慢指针的距离也减少一,只要一直移动下去,快指针总会追上慢指针。
根据上述表述得出,如果一个链表存在环,那么快慢指针必然会相遇。实现代码如下:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
bool hasCycle(ListNode *head) {
ListNode* slow = head;
ListNode* fast = head;
while (fast!= nullptr) {
fast = fast->next;
if(fast != nullptr) {
fast = fast->next;
}
if (fast == slow)
return true;
slow = slow->next;
}
return false;
}
};
拓展:双指针求解链表问题[1]
- 如果链表有环,如何求解环的长度?
方法是:快慢指针相遇后继续移动,直到第二次相遇。两次相遇间的移动次数即为环的长度。 - 链表如何获取倒数第k个元素?
先来看"倒数第k个元素的问题"。设有两个指针 p 和 q,初始时均指向头结点。首先,先让 p 沿着 next 移动 k 次。此时,p 指向第 k+1个结点,q 指向头节点,两个指针的距离为 k 。然后,同时移动 p 和 q,直到 p 指向空,此时 q 即指向倒数第 k 个结点。可以参考下图来理解:
class Solution {
public:
ListNode* getKthFromEnd(ListNode* head, int k) {
ListNode *p = head, *q = head; //初始化
while(k--) {
//将 p指针移动 k 次
p = p->next;
}
while(p != nullptr) {
//同时移动,直到 p == nullptr
p = p->next;
q = q->next;
}
return q;
}
};
- 链表如何获取中间位置的元素?
获取中间元素的问题。定义快慢指针fast和slow,设链表有 n 个元素,那么最多移动 n/2 轮。当 n 为奇数时,slow 恰好指向中间结点,当 n 为偶数时,slow 恰好指向中间两个结点的靠前一个(可以考虑下如何使其指向后一个结点呢?)。
n 为偶数时慢指针指向靠前结点。
class Solution {
public:
ListNode* middleNode(ListNode* head) {
ListNode *p = head, *q = head;
while(q != nullptr && q->next->next != nullptr) {
p = p->next;
q = q->next->next;
}
return p;
}
};
n 为偶数时慢指针指向靠后结点。
class Solution {
public:
ListNode* middleNode(ListNode* head) {
ListNode *p = head, *q = head;
while(q != nullptr && q->next != nullptr) {
p = p->next;
q = q->next->next;
}
return p;
}
};
142. 环形链表 II
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是
-1,则在该链表中没有环。注意,pos 仅仅是用于标识环的情况,并不会作为参数传递到函数中。说明:不允许修改给定的链表。
进阶:
- 你是否可以使用 O(1) 空间解决此题?
示例 1:
输入:head = [3,2,0,-4], pos = 1 输出:返回索引为 1 的链表节点 解释:链表中有一个环,其尾部连接到第二个节点。示例 2:
输入:head = [1,2], pos = 0 输出:返回索引为 0 的链表节点 解释:链表中有一个环,其尾部连接到第一个节点。示例 3:
输入:head = [1], pos = -1 输出:返回 null 解释:链表中没有环。
摘录评论区解题思路如下[2]:分两个步骤,首先通过快慢指针的方法判断链表是否有环;接下来如果有环,则寻找入环的第一个节点。具体的方法为,
- 首先假定链表起点到环入口节点的长度为x【未知】,到快慢指针相遇的节点B的长度为(x + y)【这个长度是已知的】。
- 现在我们想知道x的值,注意到快指针fast始终是慢指针skow走过长度的2倍,所以慢指针slow从相遇节点继续走(x + y)又能回到相遇节点(这个过程可能会绕环多圈),如果只走a个长度就能回到环入口节点(也可能会绕环多圈)。
- 但是a的值是不知道的,方法是曲线救国,注意到起点到环入口节点的长度是x,那么可以用一个从起点开始的新指针q和从相遇节点开始的慢指针slow同步走,相遇的地方必然是环的入口节点。
- 图解如下,来自[3]
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode* slow = head;
ListNode* fast = head;
bool hasCycle = false;
// 步骤一:使用快慢指针判断链表是否有环
while (fast != nullptr && fast->next != nullptr) {
slow = slow->next;
fast = fast->next->next;
if (fast == slow) {
hasCycle = true;
break;
}
}
// 步骤二:若有环,找到入环开始的节点
if (hasCycle) {
ListNode* q = head;
while (slow != q) {
slow = slow->next;
q = q->next;
}
return q;
} else
return nullptr;
}
};
参考
[1]一文搞定常见的链表问题 (欢迎交流
[2] https://leetcode-cn.com/problems/linked-list-cycle-ii/comments/
[3] 142. 环形链表 II :简化公式,简单易懂!