代码随想录算法训练第四天——24两两交换链表中的节点+142环形链表+19删除链表倒数第n个节点

24两两交换链表中的节点

题目要求:

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

img

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

思路

第一想法:

是否要去分块处理,因为昨天的翻转链表只需要把指针甩过来即可,所以两两去执行这个翻转操作能不能够实现呢?

困难:

  • 其实有点机会,但是甩完之后,每个两两都是独立的整体了,还要把他们拼接起来,又该如何拼接呢?

  • 以及没有想到对链表操作还是得先设置一个虚拟头结点。

正解:

  • 先设置虚拟头结点,每三个每三个进行操作。
  • 操作分为三个步骤——
  • 注意,链表在甩指针时要想到某个节点的后继找不到的情况,所以对于重点要操作的三个需要用临时节点保存起来。
  • 如何将其写成一个递归的方法,因为翻转链表里也可以写成递归。

代码:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode swapPairs(ListNode head) {
        //设置虚拟结点,每三个节点进行操作(其实是四个)
        ListNode dummyHead = new ListNode(-1);
        //这一步指向不要忘记了。
        dummyHead.next = head;
        //cur表示当前操作位置,往往在要操作的节点前一个位置
        ListNode cur = dummyHead;
        //分别保存第三个,第一个,第二个节点
        ListNode temp ,firstNode,secondNode;
        //健壮性描述
        while(cur.next != null && cur.next.next != null){
            temp = cur.next.next.next;
            firstNode = cur.next;
            secondNode = cur.next.next;
            //开始移动操作三步
            cur.next = secondNode;
            secondNode.next = firstNode;
            firstNode.next = temp;
            //移动完之后,工作指针temp也需要向后移动,移至需要更改节点之前
            cur = cur.next.next;
        }
        return dummyHead.next;
    }
}
 cur.next.next;
        }
        return dummyHead.next;
    }
}

19 删除链表的倒数第n个结点 +203 移除链表元素

题目要求

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

思路一

先算长度,再正向地去算该删除的第几个结点。

注意,链表的处理往往都需要一个哨兵结点来处理空链表的情况,需要自己新建一个空链表。

且在删除时,链表的操作指针一定要指向被删除结点的前一个

代码一(失败版)

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
//未设置哨兵的后果

struct ListNode* removeNthFromEnd(struct ListNode* head, int n){
    //先获取整个链表长度len,然后再删除第len-n个(下标从0开始),题目要求长度在1-30以内,还不大
    int len = GetLength(head);
    //要找到要删除结点的前驱后继
    struct ListNode* pre;//注意这里声明时要带struct
    struct ListNode* del;
    int j =0;
    //遍历找到前驱
    pre = head;
    while(pre && j<len-n-1){
        pre = pre->next;
        j++;
    }
    //进行删除.分情况讨论,如果只有一个元素该返回什么
    if(len == 1){
        return 
    }
    //以及可能出现要删除的地方前驱以及其后继为空的情况
    if(!pre || !pre->next){
        return head;
    }
     del =pre->next;
     pre->next = del->next;
     free(del);
     return head;
}
//获取总长度
int GetLength(struct ListNode*head){
    int i;
    int len = 0;
   struct ListNode* del;
    del = head;
    while(del){
        del = del->next;
        len++;
    }
    return len;
}

代码一(正确版)

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */


struct ListNode* removeNthFromEnd(struct ListNode* head, int n){
    //先获取整个链表长度len,然后再删除第len-n个(下标从0开始),题目要求长度在1-30以内,还不大
    int len = GetLength(head);
    //要找到要删除结点的前驱后继
    struct ListNode* pre;//注意这里声明时要带struct
    struct ListNode* del;
    int j ;
    //创建一个哨兵结点
    struct ListNode*dummy = malloc(sizeof(struct ListNode));
    dummy->val = 0,dummy->next = head;
     //注意有了哨兵之后,默认链表元素从1开始;找到要删除的前驱
    pre = dummy;
    for(j=1;j<len-n+1;j++){
        pre =pre->next;
    }
    //找到后开始删除
    pre->next = pre->next->next;
    del = dummy->next;
    free(dummy);
    return del;
}
//获取总长度
int GetLength(struct ListNode*head){
    int i;
    int len = 0;
   struct ListNode* del;
    del = head;
    while(del){
        del = del->next;
        len++;
    }
    return len;
}

思路二(双指针)

  • 设计哨兵以及快慢指针,二者初始都指向哨兵,然后快指针先走n+1步,其后快慢一起走直到快指针指向了空为止。

  • 此时的慢指针就会指向要删除结点的前一个位置。(本题的n是从1开始)

  • 注意不能对空指针进行操作。

代码二

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */


struct ListNode* removeNthFromEnd(struct ListNode* head, int n){
    //要找到要删除结点的前驱后继
    struct ListNode* fast;//注意这里声明时要带struct
    struct ListNode* slow;
    int j ;
    //创建一个哨兵结点
    struct ListNode*dummy = malloc(sizeof(struct ListNode));
    dummy->val = 0,dummy->next = head;
    fast = dummy;slow = dummy;
    //快指针先动n+1
    n++;
    while(n-- && fast){//注意这里的限制条件
        fast = fast->next;
    }
    //快慢指针一起往后走,直到快指针指向null
    while(fast){
        fast = fast->next;
        slow = slow ->next;
    }
    //slow的位置就是要删除的前一个位置
    slow->next = slow->next->next;
    struct ListNode* del = dummy->next;
    free(dummy);
    return del;
}

java版本:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        //二刷了,还是不太会,对于快慢指针都没想起来
        ListNode dummyhead = new ListNode(-1);
        dummyhead.next = head;
        ListNode fastNode = dummyhead;
        ListNode slowNode = dummyhead;
        //快指针向后走n+1步
        for(int i = 0;i < n + 1;i ++){
            fastNode = fastNode.next;
        }
        //此时快慢再一起走,当快指针为空的时候慢指针就已经走到要删除的节点之前了。
        while(fastNode != null){
            fastNode = fastNode.next;
            slowNode= slowNode.next;
        }
        //进行删除操作
        slowNode.next = slowNode.next.next;
        return dummyhead.next;
    }
}

142环形链表

题目要求:

给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null

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

不允许修改 链表。

思路:

第一想法:

  • 怎么去找这个环,链表中只有一个环吗?

困难:

  • 判断环
  • 如何找到入口处

正解:

  • 如果无环,快慢指针走完整个链表永远不可能相遇;如果有环,在相对速度为1的前提下,二者一定会在环内相遇。因为就像操场跑圈,相对速度1,快指针会一个一个地追,最终一定会追上。

  • slow 走过的距离:x+y ; fast 走过的距离:x+y+n(y+z)* ;进圈之后!!!这里慢指针不可能在圈里面走超过一圈,因为我们设定快指针速度是其两倍,相同时间路程也是两倍,当慢指针马上走完一圈时,快指针一定都走了两圈了,早就相遇了!

  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eDH9hiro-1676721314229)(C:\Users\ASUS\Pictures\Screenshots\屏幕截图_20230218_185907.png)]

  • 可以根据—时间相同,速度一个1一个2,来列出二者路程的等式——2(x+y) = x+y + n(z+y)——化简后得到**:x=(n-1)(z+y)+z**

  • 可以发现当n=1时,即走到相遇点后快指针又绕了一圈与慢指针相遇了。x=z,故等式里的前面那一坨就是快指针多绕了多少圈罢了

  • 根据x=z,在相遇点的指针和在起始的指针一起移动,一定会在环的入口处相遇。

  • 找入口就可以定义一个在相遇处的指针,一个在起始位置的指针,二者相遇的点一定是入口。

代码

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode detectCycle(ListNode head) {
        //根据思路,我们先设置快慢指针,一个速度为2一个为1
        ListNode fastNode = head;
        ListNode slowNode = head;
        //判断是否有环
        while(fastNode != null && fastNode.next != null){//因为快指针两步去走得看next
            fastNode = fastNode.next.next;
            slowNode = slowNode.next;
            //相遇之后说明有环
            if(slowNode == fastNode){
                //有环之后再去找入口,设置两个指针,一个在相遇点,一个在起始位置
                ListNode Node1 = fastNode;
                ListNode Node2 = head;
                while(Node1 != Node2){
                    //这时是公式里的x=z环节,速度是一样的
                    Node1 = Node1.next;
                    Node2 = Node2.next;
                }
                return Node1;
            }
        }
        //如果找不到环即退出返回null,其实找到环就一定会有入口的
        return null;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值