刷穿LeetCode——Task11

这篇博客记录刷题第11天的学习心得与收获。

136. 只出现一次的数字

给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

说明:

你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?

示例 1:

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

示例 2:

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

分析:这题真的是脑筋急转弯啊,不用额外空间,且应该具有线性时间复杂度 O ( n ) O(n) O(n),看了评论才知道原来是用异或来做!异或 ^ 操作有如下性质:

  1. 交换律:a ^ b ^ c <=> a ^ c ^ b

  2. 任何数与0异或为其本身 0 ^ n => n

  3. 相同的数异或为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:
    circularlinkedlist

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

示例 2:
circularlinkedlist_test2

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

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

提示:

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

分析:这里直接摘抄题解区[1], 对于链表是否有环,定义两个指针fast和slow,初始时都指向头节点,每次移动时,fast向后走两次,slow向后走一次,直到 fast 无法向后走两次。这使得在每轮移动之后。fast 和 slow 的距离就会增加1,这就是快慢指针。
当一个链表有环时,快慢指针都会陷入环中进行无限次移动,然后变成了追及问题。想象一下在操场跑步的场景,只要一直跑下去,快的总会追上慢的。当两个指针都进入环后,每轮移动使得慢指针到快指针的距离增加一,同时快指针到慢指针的距离也减少一,只要一直移动下去,快指针总会追上慢指针。
List_hasCycle
根据上述表述得出,如果一个链表存在环,那么快慢指针必然会相遇。实现代码如下:

/**
 * 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 个结点。可以参考下图来理解:

last_k_elem

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 恰好指向中间两个结点的靠前一个(可以考虑下如何使其指向后一个结点呢?)。
    MiddleNode 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:
circularlinkedlist
输入:head = [3,2,0,-4], pos = 1 输出:返回索引为 1 的链表节点 解释:链表中有一个环,其尾部连接到第二个节点。

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

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

摘录评论区解题思路如下[2]:分两个步骤,首先通过快慢指针的方法判断链表是否有环;接下来如果有环,则寻找入环的第一个节点。具体的方法为,

  • 首先假定链表起点到环入口节点的长度为x【未知】,到快慢指针相遇的节点B的长度为(x + y)【这个长度是已知的】。
  • 现在我们想知道x的值,注意到快指针fast始终是慢指针skow走过长度的2倍,所以慢指针slow从相遇节点继续走(x + y)又能回到相遇节点(这个过程可能会绕环多圈),如果只走a个长度就能回到环入口节点(也可能会绕环多圈)。
  • 但是a的值是不知道的,方法是曲线救国,注意到起点到环入口节点的长度是x,那么可以用一个从起点开始的新指针q和从相遇节点开始的慢指针slow同步走,相遇的地方必然是环的入口节点。
  • 图解如下,来自[3]

circleList_entrance

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 :简化公式,简单易懂!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值