简单记录牛客top101算法题(初级题C语言实现)BM8 链表中倒数最后k个结点 && BM10 两个链表的第一个公共结点&& BM6 判断链表中是否有环

1.BM8 链表中倒数最后k个结点

   要求:输入一个长度为 n 的链表,设链表中的元素的值为 ai ,返回该链表中倒数第k个节点。如果该链表长度小于k,请返回一个长度为 0 的链表。

输入:{1,2,3,4,5},2
返回值:{4,5}
说明:返回倒数第2个节点4,系统会打印后面所有的节点来比较。 

输入:{2},8
返回值:{}

1.1 自己的整体思路

  1. 遍历链表,记录链表长度。
  2. 再遍历链表,当指针移动的次数等于链表总长度减去倒数k时,停止遍历,返回当前指针的下一个指针即可。
    具体代码如下:
#include 
#include 
struct ListNode* FindKthToTail(struct ListNode* pHead, int k ) {
    struct ListNode* cur = pHead;
    int n = 0;            //记录链表的长度
    int n1 = 0;           //记录指针移动到哪了
    //判断链表的长度,减去后面的个数,就是前面的个数,遍历就可以
     while(cur != NULL) {
        n++;
        cur = cur->next;
    }
    cur = pHead;          //复原cur指针,指向头结点
    if ( k > n) {        //长度不够,就返回空
        return  NULL;    //空,就是代表空指针
    }
    while (cur != NULL) {               //遍历链表
         n1++;
         if ( n-k == n1  ) {            //到达指定位置
            return cur->next;
         }
         cur = cur->next;
    }
     return  pHead; 
}

1.2 其他的方法(其他大佬的方法)

  双指针法,使用两个指针,两个指针之间差距是k,遍历两个指针,当后者指向空,前者就是要返回的指针。

struct ListNode* FindKthToTail(struct ListNode* pHead, int k ) {
    // write code here
    if(pHead==NULL||k==0) return 0;   //空链表和倒数第0个节点都返回0
    struct ListNode *p=pHead,*q=pHead;   //双指针法,定义两个链表类型的指针,达到进阶的空间复杂度O(1)  
    for(int i=1;i<k;i++){                 //for循环让p和q的距离保持为k
        q=q->next;
        if(q==NULL) return 0;            //如果链表长度不够k,则返回0
    }
    while(q->next!=NULL){             //p和q同步往后走,让q指向最后一个节点,p则为倒数第k个节点
        p=p->next;
        q=q->next;
    }
    return p;                      //返回p即可
}

2.BM10 两个链表的第一个公共结点

   要求:输入两个无环的单向链表,找出它们的第一个公共结点,如果没有公共节点则返回空。(注意因为传入数据是链表,所以错误测试数据的提示是用其他方式显示的,保证传入数据是正确的)。

输入:{1,2,3},{4,5},{6,7}
返回值:{6,7}
说明:第一个参数{1,2,3}代表是第一个链表非公共部分,第二个参数{4,5}代表是第二个链表非公共部分,
      最后的{6,7}表示的是2个链表的公共部分,这3个参数最后在后台会组装成为2个两个无环的单链表,且是有公共节点的。  

       简单记录牛客top101算法题(初级题C语言实现)BM8 链表中倒数最后k个结点 && BM10 两个链表的第一个公共结点&& BM6 判断链表中是否有环_第1张图片

2.1 自己的整体思路(笨方法)

  1. 两个链表的公共结点,就是两个链表中共同指向的地址,遍历链表,把地址都存放到指针数组中,比较两指针数组,第一次出现相同的地址就是第一个公共结点,返回该地址即可。
  2. 遍历链表1,用指针数组1存储链表1的所有地址; 遍历链表2,用指针数组2存储链表2的所有地址
  3. 遍历指针数组1,依次和指针数组2的所有元素进行比较,如果相同就返回该指针地址。
    具体代码如下:
#include 
#include 
struct ListNode* FindFirstCommonNode(struct ListNode* pHead1, struct ListNode* pHead2 ) {
    // write code here
    //依次遍历,肯定不行,    公共结点后面的元素肯定是一样的       //不能反转链表
    struct ListNode* cur1 = pHead1;
    struct ListNode* cur2 = pHead2;  
    //申请两个指针数组,存下地址
    struct ListNode* arr1[1000] = {};
    struct ListNode* arr2[1000] = {};
    int n1 = 0;
    int n2 = 0;
    while (cur1 != NULL) {
        arr1[n1] = cur1;
        n1++;
        cur1 = cur1->next;  //移动指针
    }
    while (cur2 != NULL) {
        arr2[n2] = cur2;
        n2++;
        cur2 = cur2->next;  //移动指针
    }
    for (int i = 0; i <= n1; i++) {            //从0开始,这样的话,两条链表相同也是可以的
        for (int j = 0; j <= n2; j++) {
            if (arr1[i] ==  arr2[j]) {
                return arr1[i];
            }
        }
    }
     return NULL;
}

2.2 其他的方法(其他大佬的方法)

  使用双指针的方法。这种方法的核心思想是让两个指针从各自链表的头节点出发,然后分别遍历链表。当一个指针到达链表末尾时,将其重定向到另一个链表的头节点,继续遍历。这样,两个指针会分别在两个链表上遍历相同的节点数量,最终会在第一个公共节点相遇。

  1. 首先,让 p1 和 p2 分别从各自链表的头节点出发,开始遍历。
  2. 假设链表 A 的长度为 lenA,链表 B 的长度为 lenB,公共部分的长度为 lenC。
  3. 当 p1 遍历完链表 A 的所有节点,然后重新从链表 B 的头节点开始遍历,此时 p1 在链表 B 上的位置为 lenA - lenB。
  4. 同样地,当 p2 遍历完链表 B 的所有节点,然后重新从链表 A 的头节点开始遍历,此时 p2 在链表 A 上的位置为 lenB - lenA。
  5. 如果 lenA == lenB,那么在 p1 和 p2 同时从各自链表的头节点开始遍历,它们会在第一个公共节点相遇。
  6. 如果 lenA != lenB,(假设lenA>lenB)那么 p1 和 p2 在不同的节点上开始遍历,但是由于它们的步长相同,当 p1 移动了 lenB - lenC 步,p2 移动了 lenA - lenC 步,它们最终会在第一个公共节点相遇。此时p1走的步长是lenA+lenB - lenC,p2走的步长是lenB+lenA - lenC,两者是相等的,当lenA < lenB,情况是一样的。lenA = lenB的时候,不会走公共的部分。
    注意当两个链表没有公共部分的时候,其实相当于公共部分是NULL,两链表的长度相同时,就是走lenA或者lenB;两链表的长度不相同时(包括其中一个是NULL的情况),走的步长就是lenA+ lenB + 0,步长相等,指向的是NULL。
#include 
#include 
struct ListNode* FindFirstCommonNode(struct ListNode* pHead1, struct ListNode* pHead2 ) {
      struct ListNode* cur1 = pHead1;
      struct ListNode* cur2 = pHead2; 
      while (cur1 != cur2){
         if (cur1 == NULL) {
            cur1 = pHead2;
          }else {
            cur1 = cur1->next;
        }

         if (cur2 == NULL){
            cur2 = pHead1;
        }else {
           cur2 = cur2->next;
        }
    } 
    return cur1;
}

3.BM6 判断链表中是否有环

   要求:判断给定的链表中是否有环。如果有环则返回true,否则返回false。

输入:{3,2,0,-4},1
返回值:true
说明:第一部分{3,2,0,-4}代表一个链表,第二部分的1表示,-4到位置1(注:头结点为位置0),即-4->2存在一个链接,组成传入的head为一个带环的链表,返回true   

3.1 自己的整体思路(笨方法)

  1. 链表中如果有环,说明有一个结点地址被两个指针都指向,只要判断这个地址出现两次,就能判断是否有环。
  2. 申请一个指针数组,记录每一次的指针指向的结点地址。
  3. 每次记录该地址的时候,遍历前面数组中的地址,比较这次的地址是否相同,如果相同就返回true。
  4. 如果循环结束,没有找到相同的地址,就返回false。
    具体代码如下:
bool hasCycle(struct ListNode* head) {
    // write code here
    struct ListNode* cur = head; 
    //有一个地址被两个指针指向,记录前面的地址吗
    //指针数组
    struct ListNode* arr[100000] = {};
    int i = 0;                           //指针数组的索引
    while (cur != NULL) {
        arr[i] = cur;                    //记录指针的地址
        for(int j = 0; j < i;j++){       //遍历数组进行比较
           if (cur == arr[j]){           
            return true;
        }
      }
       cur = cur->next;                 //移动指针
       i++;								//索引+1
   }
    return false; 
}

3.2 其他的方法(其他大佬的方法)

  使用快慢指针的方法,也称为龟兔赛跑算法(Floyd’s cycle detection algorithm)。如果一个链表中存在环,那么快指针最终会在某一时刻追上慢指针。
    简单记录牛客top101算法题(初级题C语言实现)BM8 链表中倒数最后k个结点 && BM10 两个链表的第一个公共结点&& BM6 判断链表中是否有环_第2张图片
  如上图所示,初始指针在相同结点,快指针p2每次移动两步,慢指针移动一步,最终在第五次的移动中,快指针和慢指针又指向相同的结点。
具体代码如下:

bool hasCycle(struct ListNode* head) {
struct ListNode* p1 = head;      //声明慢指针
struct ListNode* p2 = head;      //声明快指针
// if (p1 == NULL ) {
//     return false; 
// }

// while (p2->next != NULL && p2->next->next != NULL) {
    while (p2 != NULL && p2->next != NULL) {
    p1 = p1->next;                 //慢指针移动一步
    p2 = p2->next->next;           //快指针移动两步
    if (p1 == p2) {
        return true;
    }
}
return false; 
}

   注意:p2 != NULL:判断快指针有没有到链表尾部,到了尾部就结束;p2->next != NULL:保证了快指针在每一步都至少向前移动了两步,保证它的下一个结点有值,下下个结点不管是空还是有值,都是可以移动的。这里不用保证p2->next ->next != NULL。

你可能感兴趣的:(编程题练习,算法,c语言,链表)