算法训练营Day4

#C #链表

开源学习资料

Feeling and experiences:

两两交换链表中的节点:力扣题目链接

本题的要求是要两两交换(是在不改变值的条件下),那么首先想到的问题是怎么交换?

两两交换,但如果链表节点为奇数呢?

刚开始看这个题的第一反应,就是想到了昨天做的翻转链表。虽然思路还是不够清晰,但是能确定的一点就是:一定要有临时指针来存储中间变量!

因为链表中的题,一旦涉及到了交换,删除等基本抽象操作,都要有临时变量的存储,不然节点要丢失。

最先想到的思路:既然是两两交换,那么就先模拟两个节点的交换,再推广到所有节点!

清楚了这一个问题后,开始动手画草图,模拟怎么交换:

第一步:先设置一个虚拟头节点,设置一个cur指针初始化指向该虚拟头节点。

注意:cur是指向该虚拟节点的地址,所以第一次cur指向哪里,该虚拟节点就指向哪里(后续cur要变,但虚拟节点就不会再变了)

第二步:以1,2,3,4,5为例,两两交换了就应该是2在头。

所以我们让cur指向2这个节点,因为虚拟节点本来指向1的,现在要指向2,防止1节点后续找不到,所以创建临时指针temp来存储。

前面提到了,第一次虚拟节点的指向,会随cur的改变而改变

第三步:再让2指向1,就完成2和1的交换。

但此时要注意的是,当2指向1,就会使本来指向3的指针指向了1,那么3指针及其以后的就丢失了,所以我们要用temp02临时指针来存储3这个节点。

第四步:让1指向3(因为3是被临时记录了,所以可以找到)。

最后再跟新cur指针。(因为跟新了cur指针,cur指针的地址就改变了,所以虚拟节点就不会再随着cur改变而改变了。)

那么循环结束条件是什么?

当链表节点数为偶数时,cur指向的下一个节点为空。

当链表节点数为奇数时,两两交换了,剩下的最后一个节点不交换,就是最后一个节点指向的下一个节点为空,也就是cur的指向的节点的下一个节点为空。

以下是整体过程代码:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* swapPairs(struct ListNode* head) {
    //第一步:创建一个虚拟头节点
    struct ListNode* newNode = (struct ListNode*)malloc(sizeof(struct ListNode));
    newNode->next = head;
    struct ListNode* cur = newNode;
    while(cur->next!=NULL && cur->next->next!=NULL){
        //先用两个临时变量记录要改变指向方向的节点,防止后续找不到该节点
        struct ListNode* temp = cur->next;
        struct ListNode* temp2 = cur->next->next->next;
        //第二步:
        cur->next = cur->next->next;
        //第三步:
        cur->next->next = temp;
        //第四步:
        cur->next->next->next = temp2;
        //更新cur
        cur = cur->next->next;
    }
    
    return newNode->next;

   
    
}

代码随想录中的图解:

模拟完这道题后,立刻就明白了可以用递归的方法解答。这就是一个局部推整体的过程!

这里推荐一个B站up的视频,可以帮助理解递归:视频链接

代码如下:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* swapPairs(struct ListNode* head) {
    //递归法:紧紧抓住原来的函数究竟返回的是什么?
    //递归终止条件:
    if(head == NULL || head->next == NULL){
      return head;
    }
    struct ListNode* temp = head->next;
    //接收返回值
    head->next = swapPairs(temp->next);
    temp->next = head;
    return temp;

}

关键就看接收的返回值!

 

删除链表的倒数第N个节点:力扣题目链接

该题的就非常简单了,思路就是遍历寻找到要删除节点的前一个节点。

就是一个基础的删除节点题目:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* removeNthFromEnd(struct ListNode* head, int n) {
       struct ListNode* temp = malloc(sizeof(struct ListNode));
       temp->next = head;
       int length = getLength(head);
       struct ListNode* cur = temp;
       //循环走到要删除的节点的前一个节点
       for(int i = 1;i<=length-n;++i){
              cur = cur->next;
       }
       cur->next = cur->next->next;
       return temp->next;


}
 int getLength(struct ListNode* head){
     int length = 0;
     while(head){
         ++length;
         head = head->next;
     }
     return length;
     
 }

后面再思考一下递归的写法!

链表相交:力扣题目链接

 找相交的节点,最开始还有点没读懂这道题是什么意思

思路为,定义两个指针,找到指针相等的点,则为交点。

后面明白了,关键就在于如果指针相等(这不代表值相等),而是地址相同,则代表两个指针指向同一个地址,则说明该点为交点!

示例:

代码随想录中的图解:

代码如下:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode curA = headA;
        ListNode curB = headB;
        int lenA = 0, lenB = 0;
        while (curA != null) { // 求链表A的长度
            lenA++;
            curA = curA.next;
        }
        while (curB != null) { // 求链表B的长度
            lenB++;
            curB = curB.next;
        }
        curA = headA;
        curB = headB;
        // 让curA为最长链表的头,lenA为其长度
        if (lenB > lenA) {
            //1. swap (lenA, lenB);
            int tmpLen = lenA;
            lenA = lenB;
            lenB = tmpLen;
            //2. swap (curA, curB);
            ListNode tmpNode = curA;
            curA = curB;
            curB = tmpNode;
        }
        // 求长度差
        int gap = lenA - lenB;
        // 让curA和curB在同一起点上(末尾位置对齐)
        while (gap-- > 0) {
            curA = curA.next;
        }
        // 遍历curA 和 curB,遇到相同则直接返回
        while (curA != null) {
            if (curA == curB) {
                return curA;
            }
            curA = curA.next;
            curB = curB.next;
        }
        return null;
    }

}

 没有涉及到指针的重要用法,就用Java代码写了。

环形链表II:力扣题目链接

主要问题在于:

是否存在有环?

如果有环,怎么找到那个出口?

使用快慢指针的思想,如果慢指针速度为1,快指针速度为2,那么它们的相对速度为1,则如果存在环,它们一定会相遇。

代码随想录中的图解:

以此图为例,用到一些数学知识:

首先,声明慢指针slow速度为1,快指针fast速度为2。

假设在第二个方块处相遇(环内随机一个位置),此时,slow走过的节点数为x+y。

fast走过的节点数为x+n(z+y),其中n为圈数,大于等于1(因为至少fast要跑一圈才能相遇,slow从环入口进来,速度慢,一圈是追不上的)

而fast速度是slow的两倍,所以走过的节点数也是slow的两倍,综上有等式:

2*(x+y) = x+n(z+y) =》 整理得:x=nz-(n-1)y

而这个x,不就是入口节点的位置嘛!

但是后面还有z,y,还不看不出来什么。

举个特殊例子,当n等于1时,说明fast跑了一圈就和slow相遇了,此时x=z。

我们再结合图来看一下,x=z,那岂不是就意味着相遇点到入口的距离和头节点到入口的距离相等?

那么思路一下就清晰了,当fast == slow 的时候,也就是相遇点。

记录这个相遇点,让两个速度为1的指针,一个从相遇点走,一个从头节点走,它们相遇,就找了入口!

思路代码如下:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode *detectCycle(struct ListNode *head) {
    //先设置一个快指针,和一个慢指针
    struct ListNode* fast = head;
    struct ListNode* slow = head;
   

    //找到slow , fast 相遇点
   while(fast != NULL && fast->next != NULL) {
        //设置它们的速度,fast一下走两节点,slow一下走一个节点
    fast = fast->next->next;//代表速度为2
    slow = slow->next;//代表速度为1

    if(fast == slow){
        //找到了相遇点
        //则从这个相遇点设置一个指针速度为1,头节点再设置一个指针速度为1,让它们开始运动
        struct ListNode* index01 = fast;
        struct ListNode* index02 = head;
        //当这两个指针相遇了,则该相遇点为入口
        while(index01 != index02){
        index01 = index01->next;
        index02 = index02->next;
        }
        return index01;
        
    }
    }
    return NULL;

}

这道题真的非常有意思,夹杂数学思想,太crazy了 !

从今天练的题目总结下来,双指针运用广泛,要熟练掌握。(特别是数组和链表中的题)

唯愿时光清浅,将你温柔以待。

不问开花几许,只愿浅笑安然~

Fighting!

  • 19
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
邓俊辉教授是计算机科学与技术领域著名的教育家和研究者。他在清华大学担任教授,并负责计算机算法与理论方向的研究和教学工作。邓俊辉教授是中国计算机学会副理事长、国际著名科技出版社Springer中国系列丛书主编、IEICE China Communications主编、Journal of Internet Technology编委、《数据结构与算法教程》作者等。 在邓俊辉教授的指导下,他办了多次Dijkstra算法训练营,旨在培养学生对于算法学习的兴趣与能力。Dijkstra算法是一种用于图论中求解最短路径问题的经典算法,具有广泛的应用领域,如路由算法、网络规划和GPS导航系统等。在训练营中,邓俊辉教授通过讲解算法的原理和思想,引导学生进行编程实践和案例分析,帮助他们深入理解Dijkstra算法的应用场景与实际解决问题的能力。 邓俊辉教授所组织的Dijkstra算法训练营受到了广大学生的欢迎和积极参与。通过训练营的学习,学生不仅可以掌握Dijkstra算法的具体实现过程,还能了解算法设计的思路和应用的局限性。在训练营中,学生还可以与同学们进行交流和合作,共同解决实际问题,促进彼此的学术成长和人际交往能力的培养。 总之,邓俊辉的Dijkstra算法训练营为学生提供了一个系统、全面学习算法知识的平台,帮助他们培养解决实际问题的能力和思维方式。通过这样的培训,学生不仅能在学术领域取得更好的成绩,还可以为将来的职业发展打下坚实的基础。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值