LeetCode 137 Single Number II
方法:位运算
时间复杂度:O(n),其实是O(32n) 空间复杂度:O(1) 想法:位运算题目里面的常见做法之一,就是按照每一位数数。因为题目说里面的数字只有一个出现了一次,其他都出现了正好3次,那么我枚举32位,每一位都对数组当中所有数数一下有多少个1,假设说某一位上总共的1不是3的倍数,那就说明那个只出现了一次的数,在这一位上是1,否则是0。这样就构造出来了这个数。 代码:
class Solution {
public int singleNumber(int[] nums) {
int res = 0;
for (int i = 0; i < 32; i++) {
int sum = 0;
for (int num : nums) {
sum += (num >> i) & 1;
}
if (sum % 3 != 0) {
res |= (1 << i);
}
}
return res;
}
}
LeetCode 260 Single Number III
方法:位运算
时间复杂度:O(n) 空间复杂度:O(1) 想法:这个题要灵活运用Single Number I的方法。Single Number I因为在这样一个年代已经非常简单了,因此没写题解,但是它是说一个数组里只有一个数出现了一次,其他的都出现了刚好两次。用的是异或的性质,因为a^a=0,所以那道题是直接把数组里面的数全异或起来就完了。那么对于这道题,有两个数出现了刚好一次,如果全异或起来会得到它俩的异或,单说这个值并没有什么用。但是,假如说我能够把原数组分成两个组,这俩落单的元素各在一个组。然后对于其他元素,都成对地出现在同一个组,那么把两个分组分别全部异或就结束了。问题就在于如何分成两个组。做法是采用lowbit操作,lowbit操作模板是x & (-x),这个很常用,在树状数组里天天用,作用就是取出一个数的最低的为1的那个位。那么,假如说对于这俩落单的值异或出来的值叫xor,然后对xor做lowbit,那就是说xor在这一位为1,那就是说明两个落单的元素在这一位值不同,那就可以根据这一位是0还是1给原数组分组,保证两个落单的值分别去两个组。 代码:
class Solution {
public int[] singleNumber(int[] nums) {
int xor = 0;
for (int num : nums) xor ^= num;
int low = xor & (-xor);
int group0 = 0, group1 = 0;
for (int num : nums) {
if ((low & num) == 0) {
group0 ^= num;
}
else {
group1 ^= num;
}
}
return new int[] {group0, group1};
}
}
LeetCode 142 Linked List Cycle II
方法:同向双指针
时间复杂度:O(n) 空间复杂度:O(1) 想法:这个年代人人会做的题,主要是写一下推导。参考自yxc大佬www.acwing.com/solution/co… ,但是我需要继续证明C点相遇的时候slow只在走第一圈,不然会影响时间复杂度。 假设各点如图:
两个指针都从A点出发,slow每次走一步,fast每次走两步,在C点相撞。在C点相撞是假设slow已经在环上转了n圈,fast已经在环上转了m圈,那么 x + (y + z) * m + y = 2 * [x + (y + z) * n + y] 则 x = (m - 2n) * (y + z) - y = (m - 2n - 1) * (y + z) + z 说明x是圆环周长的整数倍加z。这个时候我们如果让slow回head,并且fast就从C点走,那么当slow走到B点时,fast刚好也到B点,这里就是要求的点。总结:先是slow和fast走的快慢不一样直到指向同一地址,然后把其中一个挪到x,两个指针每次只走一步找B点。 那么为什么第一次在C点相遇的时候,slow一定还没有走完第一圈呢?假如slow到B点的时候,fast在D点,离slow的距离是l。那么我们知道slow每次走一步,fast每次走两步。在l个单位时间之后,slow和fast会相遇,那时候slow走了l步,l小于圆的周长。因此相遇时slow在转第一圈,因此时间复杂度是O(n)。 代码:
public class Solution {
public ListNode detectCycle(ListNode head) {
if (head == null || head.next == null) return null;
ListNode slow = head, fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) {
break;
}
}
if (slow != fast) return null;
slow = head;
while (slow != fast) {
slow = slow.next;
fast = fast.next;
}
return slow;
}
}
LeetCode 287 Find the Duplicate Number
方法1:位运算
时间复杂度:O(n) 空间复杂度:O(1) 想法:使用Single Number II类似做法,每一位数过去。题目说n + 1个值,值域[1, n],那么刚好只有一个数重复出现。那就枚举每一位,把[1,n]范围内总共这一位有多少个1统计一下(cnt1),再把原数组里面这一位有多少个1统计一下(cnt2),如果cnt2 > cnt1说明多出来的数在这一位是1,像这样构造结果。 代码:
class Solution {
public int findDuplicate(int[] nums) {
int res = 0, n = nums.length;
for (int i = 0; i < 32; i++) {
int bit = 1 << i;
int cnt1 = 0, cnt2 = 0;
for (int k = 0; k < n; k++) {
if ((k & bit) > 0) cnt1++;
if ((nums[k] & bit) > 0) cnt2++;
}
if (cnt2 > cnt1) res += bit;
}
return res;
}
}
方法2:同向双指针
时间复杂度:O(n) 空间复杂度:O(1) 想法:使用Linked List Cycle II的方法,这个方法我感觉现场想把这两道题联系到一起还是超级有洞察力的...反正我当时没想到,看了解答之后觉得非常有道理。
- 因为所有数字只能是[1,n]之间,然后有n + 1个数,比方说4个数,值域[1,3]的整数,那么对于数组中任何一个数num, 做nums[num]的访问不会出现索引越界。
- 假如说对于数组中的数num,构造一个链表节点,它的值是num,它的next指向nums[num],那么我们可以构造出来一个假想的链表。因为中间有重复值,所以会有这样一个节点,有两个节点指向它。
- 如上所述构成了Linked List Cycle II,2.中描述的那个节点就是环的入口。
- 因此套用Linked List Cycle II的做法,只不过把node.next变成node = nums[node]。 代码:
class Solution {
public int findDuplicate(int[] nums) {
int slow = 0, fast = 0;
int n = nums.length;
while (true) {
slow = nums[slow];
fast = nums[nums[fast]];
if (slow == fast) {
break;
}
}
fast = 0;
while (slow != fast) {
slow = nums[slow];
fast = nums[fast];
}
return slow;
}
}