代码随想录算法训练营第四天|24.两两交换链表中的节点、19.删除链表的倒数第N个结点、160.链表相交、 142.环形链表II

24.两两交换链表中的节点

题目链接:24. 两两交换链表中的节点 - 力扣(LeetCode)
题目描述:给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。

示例 1:

img

输入:head = [1,2,3,4]
输出:[2,1,4,3]

示例 2:

输入:head = []
输出:[]

示例 3:

输入:head = [1]
输出:[1]

提示:

  • 链表中节点的数目在范围 [0, 100]
  • 0 <= Node.val <= 100

虚拟头结点

解法一、正常采用临时变量交换

在这里插入图片描述

struct ListNode* swapPairs(struct ListNode* head) {
typedef  struct ListNode ListNode;
   ListNode *fakehead,*cur;
   fakehead=(ListNode*)malloc(sizeof(ListNode));
   fakehead->next=head;
   cur=fakehead;
   while(cur->next&&cur->next->next){
       ListNode *temp=cur->next;
       ListNode *temp1=cur->next->next->next;
       cur->next = cur->next->next;    // 步骤一
       cur->next->next = temp1;          // 步骤二
       cur->next->next->next = temp2;   // 步骤三
       cur = cur->next->next; // cur移动两位,准备下一轮交换
   }
    return fakehead->next;
}

解法二、双指针迭代,不用临时变量

struct ListNode* swapPairs(struct ListNode* head) {
typedef  struct ListNode ListNode;
   ListNode *fakehead,*p,*q;
   fakehead=(ListNode*)malloc(sizeof(ListNode));
   fakehead->next=head;
   p=fakehead->next;
   q=fakehead;
   while(p&&q&&p->next){
       q->next=p->next;   //步骤一
       p->next=q->next->next;//步骤二
       q->next->next=p;//步骤三
       q=p;//更新指针
       p=q->next;
   }
    return fakehead->next;
}

解法三、递归

struct ListNode* swapPairs(struct ListNode* head){
    //递归结束条件:头节点不存在或头节点的下一个节点不存在。此时不需要交换,直接返回head
    if(!head || !head->next)
        return head;
    //创建一个节点指针类型保存头结点下一个节点
    struct ListNode *newHead = head->next;
    //更改头结点+2位节点后的值,并将头结点的next指针指向这个更改过的list
    head->next = swapPairs(newHead->next);
    //将新的头结点的next指针指向老的头节点
    newHead->next = head;
    return newHead;
}

19.删除链表的倒数第N个结点

题目链接:19. 删除链表的倒数第 N 个结点 - 力扣(LeetCode)
题目描述:给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

示例 1:

img

输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]

示例 2:

输入:head = [1], n = 1
输出:[]

示例 3:

输入:head = [1,2], n = 1
输出:[1]

提示:

  • 链表中结点的数目为 sz
  • 1 <= sz <= 30
  • 0 <= Node.val <= 100
  • 1 <= n <= sz

解法一、两次遍历

第一次遍历计算链表长度,第二次遍历找到需要删除结点的前驱结点
采用虚拟头结点的方式,避免单独处理删除头结点的情况

struct ListNode* removeNthFromEnd(struct ListNode* head, int n) {
  typedef struct ListNode ListNode;
  //虚拟头结点
  ListNode *dummy=(ListNode*)malloc(sizeof(ListNode));
  dummy->next=head;
  ListNode *p,*temp,*q;
  p=dummy->next;
  int cnt=0;
  while(p){//计算链表长度
      cnt++;
      p=p->next;
  }
  p=dummy;
  for(int i=1;i<cnt+1-n;i++){//寻找被删除结点的前驱结点
      p=p->next;
  }
  p->next=p->next->next;
    return dummy->next;//返回头结点
}

解法二、快慢双指针

定义快慢指针fast、slow,fast一直超前slown+1个结点,这样当fast走到链表末尾时,slow正好处于倒数第n+1个结点,即被删除结点的前驱结点。

struct ListNode* removeNthFromEnd(struct ListNode* head, int n) {
  typedef struct ListNode ListNode;
  ListNode *fast,*slow,*dummy;
  dummy=(ListNode*)malloc(sizeof(ListNode));
  dummy->next=head;//虚拟头结点指向head
  fast=head,slow=dummy;//初始化快慢指针
  //由于slow初始指向虚拟头结点,比fast初始慢1步,fast只需要走先走n步即可
  for(int i=0;i<n;i++){
      fast=fast->next;
  }
  while(fast){
      fast=fast->next;
      slow=slow->next;
  }
  slow->next=slow->next->next;//删除链表的倒数第N个结点
  ListNode *ans=dummy->next;
  free(dummy);
    return ans;
}

160.链表相交

题目链接:面试题 02.07. 链表相交 - 力扣(LeetCode)
题目描述:给你两个单链表的头节点 headAheadB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null

图示两个链表在节点 c1 开始相交**:**

img

题目数据 保证 整个链式结构中不存在环。

注意,函数返回结果后,链表必须 保持其原始结构

示例 1:

img

输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at '8'
解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。
在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。

示例 2:

img

输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Intersected at '2'
解释:相交节点的值为 2 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4]。
在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。

示例 3:

img

输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。
由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
这两个链表不相交,因此返回 null 。

提示:

  • listA 中节点数目为 m
  • listB 中节点数目为 n
  • 0 <= m, n <= 3 * 104
  • 1 <= Node.val <= 105
  • 0 <= skipA <= m
  • 0 <= skipB <= n
  • 如果 listAlistB 没有交点,intersectVal0
  • 如果 listAlistB 有交点,intersectVal == listA[skipA + 1] == listB[skipB + 1]

思路

分别计算两个链表的长度,然后末尾对齐,再遍历判断是否相交。
注意:判断是否相交是看指针是否相等而不是结点数值是否相等

//(末尾对齐即得到两者长度之差再移动长链表指针和短链表头指针对齐,寻找相交处即curA和curB同时后移看什么时候地址相同)
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    typedef struct ListNode ListNode;
    ListNode* curA=headA;
    ListNode* curB=headB;
    int lenA=0;
    int lenB=0;
    while(curA){
        lenA++;
        curA=curA->next;
    }
    while(curB){
        lenB++;
        curB=curB->next;
    }
    //末尾对齐时把长链表的头指针命名为curA指针,可以仅写一遍移动指针的代码即只移动curA
    int gap=0;
    if(lenA>lenB){
        curA=headA;
        curB=headB;
        gap=lenA-lenB;
    }else{
        curA=headB;
        curB=headA;
        // int gap=lenB-lenA;
        gap=lenB-lenA;
    }
    while(gap--) curA=curA->next;
    while(curA!=NULL&&curB!=NULL){
        if(curA==curB){
            return curA;
        }
        curA=curA->next;
        curB=curB->next;
    }
        return NULL;
}

142.环形链表II

题目链接:142. 环形链表 II - 力扣(LeetCode)
题目描述:给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos-1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

不允许修改 链表。

示例 1:

img

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

示例 2:

img

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

示例 3:

img

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

提示:

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

解法一、哈希表

遍历链表中的每一个结点并加入到哈希表,一旦遇到哈希表中已经存在的结点,就可以判断链表中存在环,且环入口就是该结点。

#include "uthash.h"
struct ListNode {
    int val;
    struct ListNode *next;
};
typedef struct {
    struct ListNode* key;
    UT_hash_handle hh;
}HashSet;
HashSet *g_head;
HashSet *find(struct ListNode* ikey){
    HashSet *s;
    HASH_FIND_PTR(g_head,&ikey,s);
    return s;
}
void Add(struct ListNode* ikey){
    HashSet *s=(HashSet*)malloc(sizeof(HashSet));
    s->key=ikey;
    HASH_ADD_PTR(g_head,key,s);
}
struct ListNode *detectCycle(struct ListNode *head) {
    typedef  struct ListNode ListNode;
    g_head=NULL;
    while(head){
        if(find(head)){
            return head;
        }
        Add(head);
        head=head->next;
    }
    return NULL;
}

解法二、快慢双指针

思路

1.如何判断链表是否有环
分别定义 fastslow 指针,从头结点出发,fast指针每次移动两个节点slow指针每次移动一个节点,如果 fastslow指针在途中相遇 ,说明这个链表有环
(为什么相遇就是有环?这是数学上追赶问题,如果有环,在环内速度快的一定会追上速度慢的)
2.如果有环,如何找到环的入口?

假设从头结点到环形入口节点的节点数为x。环形入口节点到fast指针与slow指针相遇节点 节点数为y。从相遇节点 再到环形入口节点节点数为z。
那么相遇时,slow指针走过的节点数为: x + y, fast指针走过的节点数:x + y + n (y + z),n为fast指针在环内走了n圈才遇到slow指针,(y+z)为 一圈内节点的个数A。
在这里插入图片描述

因为fast指针是一步走两个节点,slow指针一步走一个节点,所以fast指针走过的节点数 = slow指针走过的节点数 * 2:(x + y) * 2 = x + y + n (y + z), 化简得x = n (y + z) - y

x = (n - 1) (y + z) + z,注意这里n一定是大于等于1的,因为 fast指针至少要多走一圈才能相遇slow指针。

n=1时,x=z,表示fast指针在环里转了一圈就与slow相遇了。这就意味着,从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点

在相遇节点处,定义一个指针index1,在头结点处定一个指针index2。让index1,index2同时移动,每次移动一个节点, 那么他们相遇的地方就是 环形入口的节点。

n如果大于1,就是fast指针在环形转n圈之后才遇到slow指针,index1 指针在环里 多转了(n-1)圈,然后再遇到index2,相遇点依然是环形的入口节点

代码

ListNode *detectCycle(ListNode *head) {
    ListNode *fast = head, *slow = head;
    while (fast && fast->next) {
        slow = slow->next;
        fast = fast->next->next;
        // 相交,开始找环形入口:分别从头部和从交点出发,找到相遇的点就是环形入口
        if (slow == fast) { 
            ListNode *f = fast, *h = head;
            while (f != h) f = f->next, h = h->next;
            return h;
        }
    }
    return NULL;
}
  • 23
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值