数据结构第二天之链表

第二天——链表

其实链表这个东西,比较简单吧。一个数据域,一个指针域,常用的也就那么几种解决办法,双指针啥的。但是鉴于我是Java转C++,对我来说这个玩意的难点在于记得释放申请的空间。所以就练习一下并记录。

移除链表元素

力扣原题

这种题就没啥难度,唯一的问题在于删除第一个节点时需要返回新的头节点。一个合理的方法是设置一个虚拟头节点统一删除操作,这样最后只需返回虚拟头节点的指向节点就行。

代码如下:

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        ListNode* h = new ListNode(0); // 虚拟头节点
        h->next = head;
        ListNode* p = h;
        while (p->next != NULL) {
            if (p->next->val == val) {
                ListNode* tmp = p->next;
                p->next = p->next->next;
                delete tmp; // 注意这里与Java的不同,需要读取删除节点手动删除
            }
            else
            {
                p = p->next;
            }
        }
        head = h->next;
        delete h; // 删除虚拟头节点
        return head;
    }
};

翻转链表

力扣原题

翻转链表确实是常规题目了属于是,搞三个节点分别记录当前节点、下一个节点以及前一个节点然后逐个翻转即可。

代码如下:

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* pre = NULL;
        ListNode* tmp;
        ListNode* cur = head;
        while (cur)
        {
            tmp = cur->next; // 保存下一个节点
            cur->next = pre; // 翻转
            pre = cur;
            cur = tmp;
        }
        return pre;
    }
};

两两交换链表的节点

力扣原题

这题还是有点难度的,因为单链表需要保持前后连接需要记录,而两两交换决定了交换如果要完成需要中介。这时考虑添加一个虚拟头节点。以V->1->2->3->L为例说明我们的交换流程。先记录下需要拆开的节点V->next和V->next->next->next分别为t1和t2。

第一步 连接v和2

操作完的链表结构:V->2

此时记录t1为1

记录t2为3->L

第二步 连接2和t1

操作完的链表结构:V->2->1

此时还有记录t2:3->L

第三步 连接t1和t2

操作完的链表结构:V->2->1->3->L

然后我们将指向V的指针往后移动两项即可。

代码如下:

class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        ListNode* h = new ListNode(0); // 设置一个虚拟头结点
        h->next = head;
        ListNode* cur = h;
        while (cur->next != NULL && cur->next->next != NULL) {
            ListNode* t1 = cur->next; // 记录临时节点
            ListNode* t2 = cur->next->next->next; // 记录临时节点

            cur->next = cur->next->next;    // 步骤一
            cur->next->next = t1;          // 步骤二
            cur->next->next->next = t2;   // 步骤三

            cur = cur->next->next; // cur移动两位
        }
        return h->next;
    }
};

删除链表的倒数第N个节点

简简单单的一道题,由于链表不像顺序表可以直接查位置,所以需要双指针法来找到位置。具体的操作就是快指针走N步然后快慢指针同时往后走,快指针走到头了直接删慢指针就行。这里注意如果N为表长需要处理头节点删除。

代码如下:

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* fast = head;
        ListNode* slow = head;
        int k = 1, s = n; // 这里k记录表长,s用于暂存需要走的步数
        if (head == nullptr)
        {
            return nullptr;
        }
        while (fast->next!=nullptr)
        {
            if (s == 0) // 快慢指针距离差为n时,两指针一起走
            {
                slow = slow->next;
                fast = fast->next;
            }
            else // 否则就快指针往后走,记录走过的步长
            {
                fast = fast->next;
                s--;
            }
            k++;
        }
        if (k == n) // 特殊处理删除第一个元素
        {
            ListNode* tmp = head->next;
            delete head;
            return tmp;
        }
        else
        {
            ListNode* tmp = slow->next;
            slow->next = tmp->next;
            delete tmp;
            return head;
        }
    }
};

链表相交

力扣原题

如果两个链表相交,则必然从某一个节点开始到末尾有两表的指针相等。所以我们考虑先将两个链表末尾对其,然后求出两个链表的长度差值k,让长的链表指针先走k步,再同时移动,这样指针相等的节点就是交点了。

代码如下:

class Solution {
public:
    ListNode* getIntersectionNode(ListNode* headA, ListNode* headB) {
        ListNode* ap = headA;
        ListNode* bp = headB;
        int lenA = 0, lenB = 0;
        int k = 0;
        while (ap != nullptr) // A链表求长度
        {
            lenA++;
            ap = ap->next;
        }
        while (bp != nullptr) // B链表求长度
        {
            lenB++;
            bp = bp->next;
        }
        ap = headA;
        bp = headB;
        if (lenA > lenB) // 找到长的链表让他先走
        {
            k = lenA - lenB;
            while (k != 0)
            {
                ap = ap->next;
                k--;
            }
            while (ap != nullptr && bp != nullptr)
            {
                if (ap == bp)
                {
                    return ap;
                }
                ap = ap->next;
                bp = bp->next;
            }
        }
        else
        {
            k = lenB - lenA;
            while (k != 0)
            {
                bp = bp->next;
                k--;
            }
            while (ap != nullptr && bp != nullptr)
            {
                if (ap == bp)
                {
                    return ap;
                }
                ap = ap->next;
                bp = bp->next;
            }
        }
        return nullptr;
    }
};

环形链表

力扣原题

这种题我是最不擅长的,虽然本质还是双指针,但是其实开始和结束条件没有那么直观,因此我们可以画图尝试去理解。

首先,怎么知道链表是否有环

由于双指针是快慢两个指针,不如我们考虑一条赛道上赛跑的两个人。如果一个人跑步速度始终比另一个人快,那么如果赛道是环形的,假以时日他就会追上慢的那个人。否则他们将不会再相遇。这时我们就知道了链表有环的判断条件:快慢指针在快指针到达出口前相遇。

然后是环的入口

老实讲,一开始我是真没啥头绪,但是如果你在纸上画出一个例子并且自己尝试模拟情况,你就会发现,由于快指针先进入了环,实际上就会在环内循环走并等待慢指针。这时慢指针再进入环,其相遇的节点距离环的入口一定是 环前长度%环的大小

这时我们再从相遇节点开始,一个指针设为链表头,一个指向相遇节点,两个都每次移动一步,再次相遇时我们就得到了环的入口。

代码如下:

class Solution {
public:
    ListNode* detectCycle(ListNode* head) {
        ListNode* fast = head;
        ListNode* slow = head;
        while (fast != nullptr && fast->next != nullptr)
        {
            slow = slow->next; // 快走两步,慢走一步
            fast = fast->next->next;
            if (fast == slow) // 有环重设一个指针位置,然后两个都只走一步找入口
            {
                fast = head;
                while (fast != slow)
                {
                    fast = fast->next;
                    slow = slow->next;
                }
                return slow;
            }
        }
        return nullptr;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值