简单记录牛客top101算法题(初级题C语言实现)BM4 合并两个排序的链表 && BM13 链表是否为回文结构&& BM15 删除有序链表中重复的元素

1. BM4 合并两个排序的链表

   要求:输入两个递增的链表,单个链表的长度为n,合并这两个链表并使新链表中的节点仍然是递增排序的。
      简单记录牛客top101算法题(初级题C语言实现)BM4 合并两个排序的链表 && BM13 链表是否为回文结构&& BM15 删除有序链表中重复的元素_第1张图片

输入:{1,3,5},{2,4,6}
返回值:{1,2,3,4,5,6}

1.1 自己的整体思路

1.定义一个新的链表,使用新的链表进行重新指向。
2. 分三种情况讨论:第一种情况:链表1为空,链表2不为空;第二种情况:链表1不为空,链表2为空;第三种情况:链表1和链表2都为空或者都不为空的情况。
3. 在第三种情况中,要考虑到比较链表中的元素时,链表1中的元素遍历完还是链表2中的数据先遍历完成。
4. 两个核心是指针的指向问题和指针的移动问题。

具体代码如下:

struct ListNode* Merge(struct ListNode* pHead1, struct ListNode* pHead2) {
    struct ListNode* cur1 = pHead1;         //定义链表1当前一个结点

    struct ListNode* cur2 = pHead2;         //定义链表2当前一个结点

    //只有在头插或者尾插的时候才需要定义两个结点
    struct ListNode *newNode = (struct ListNode *)malloc(sizeof(struct ListNode));      //增加一个新的结点
    newNode->next = NULL;                                                               //新结点指向NULL
    struct ListNode *newNode1 = newNode;                                                //定义一个指针指向该头结点
    
    // struct ListNode *newNode1 = (struct ListNode *)malloc(sizeof(struct ListNode));      //增加一个新的结点
    struct ListNode *newNode1 = newNode;

    if(cur1 == NULL && cur2 != NULL){             //第一种情况:链表1为空,链表2不为空
        newNode1->next = cur2;

    }else if (cur2 == NULL && cur1 != NULL) {     //第二种情况:链表1不为空,链表2为空
        newNode1->next = cur1;
    
    }else {                                      //第三种情况:链表1和链表2都为空或者都不为空
      while (cur1 != NULL ){
        if (cur1->val <= cur2->val) {             //依次判断元素大小         
            newNode->next = cur1;
            
            newNode = newNode->next;              //移动指针
            
            cur1 = cur1->next;                   //移动指针
        }else {                                   //这个里面的链表2不能是空  1,16,19和16,16,16的情况
             newNode->next = cur2;                //指向链表2的元素

             newNode = newNode->next;             //移动指针

             cur2 = cur2->next;

              if(cur2 == NULL){
                 break;                                //1还没有遍历完,但是2已经完了,直接结束整个循环
              }
        }
    }
    while (cur1!= NULL){                       //代表链表1还有剩余
        newNode->next = cur1;                 //指向链表1的剩余元素

        newNode = newNode->next;              //移动指针
 
        cur1 = cur1->next;                    //移动指针
    }

    while (cur2!= NULL){				   //代表链表2还有剩余
        newNode->next = cur2;              //指向链表2的剩余元素

        newNode = newNode->next;           //移动指针
 
        cur2 = cur2->next; 				   //移动指针
    }
 }
    return  newNode1->next;
}

优化上面代码:

struct ListNode* Merge(struct ListNode* pHead1, struct ListNode* pHead2) {
	struct ListNode* cur1 = pHead1;       //定义当前一个结点
    //定义链表2的前一个结点和当前结点
    // struct ListNode *pre2 = NULL;       //定义前一个结点
    struct ListNode* cur2 = pHead2;        //定义当前一个结点

    //只有在头插或者尾插的时候才需要定义两个结点
    struct ListNode *newNode = (struct ListNode *)malloc(sizeof(struct ListNode));      //增加一个新的结点
    newNode->next = NULL;
    struct ListNode *newNode1 = newNode;

    if(cur1 == NULL){                           //这里包含了两个链表都为NULL的情况
        newNode1->next = cur2;

    }else if (cur2 == NULL) {                   //这里包含了两个链表都为NULL的情况
        newNode1->next = cur1;
    
    }else {                                      //都不为空的情况     
     while (cur1 != NULL && cur2 != NULL){
    
        if (cur1->val <= cur2->val) {             //判断第一个元素, 1链表的第一个元素小           
            
            newNode->next = cur1;

            newNode = newNode->next;              //移动结点

            cur1 = cur1->next;
        }else {                                   //这个里面的链表2不能是空  1,16,19和16,16,16的情况

             newNode->next = cur2;                //指向链表2的第一个元素

             newNode = newNode->next;             //移动结点

             cur2 = cur2->next;
        }
    }
    if (cur1!= NULL) {                     //不再需要用while进行遍历了,直接指向就可以,不用管后面有多少个元素 ,因为1和2的链表还没有断                   
       newNode->next = cur1;               //指向链表1的指针,这里不管链表1还有几个元素
    }

    if (cur2!= NULL) {                    
       newNode->next = cur2;               //指向链表2指针,这里不管链表2还有几个元素
    }
 }
    return  newNode1->next;
}

1.2 小结

1.2.1 申请一个空链表

    struct ListNode *newNode = (struct ListNode *)malloc(sizeof(struct ListNode));      //申请一个新的结点
    newNode->next = NULL;                                                               //新结点指向NULL
    struct ListNode *newNode1 = newNode;                                                //定义一个指针指向该头结点

1.2.2 链表的指针指向和移动

  newNode->next = cur1;             //指针指向
  newNode = newNode->next;          //移动指针

  

2. BM13 判断一个链表是否为回文结构

   要求:给定一个链表,请判断该链表是否为回文结构。 回文是指该字符串正序逆序完全一致。

输入:{1,2,2,1}
返回值:true
说明:1->2->2->1     

2.1 自己的整体思路

1.遍历链表,求出链表的长度。
2. 因为回文链表是对称的,所以要找到链表对称的位置。再遍历一遍链表,找到链表对称的位置,并记录下对称位置的指针,如果是4(偶数)个元素,就记录3(偶数 / 2的下一个位置)的指针位置,如果是7(奇数)个元素,就记录4(奇数 / 2的下一个位置)。
3. 反转对称位置的指针后面的链表,然后进行依次比较,如果都相同返回true,有一个不相同,就返回false。

具体代码如下:

#include 
#include 
bool isPail(struct ListNode* head) {
    int n = 0;                         //链表总长度
    int n1 = 0;                        //链表指针位置

    struct ListNode* cur = head;           //定义当前指针
    struct ListNode* double_p ;            //定义双指针(对称位置的指针)
    struct ListNode* double_p_pre ;        //定义双指针的前一个指针  
    struct ListNode* double_p_next;        //定义双指针的后一个指针 
    
    //链表不能用数组的方法,因为如果把链表的值,存放到数组中还要去考虑是否为奇数,是否为偶数
    //遍历链表,计算出链表长度
    while (cur!=NULL ) {
        n++;
        cur = cur->next;
    }
    cur = head;   //复原头指针
    if (n==1) {
        return true;
    }
    while (cur!=NULL) {             //再遍历一遍,找到n/2的指针
        n1++;                       //就是要从1开始进行
        if( n1 == n/2){             //找到双指针的位置在哪
            double_p = cur->next;   //记录位置,这里是n/2的后一个结点
            cur->next = NULL;       //前面链表的指向变成空,这时候,链表就断掉了
            break;
        }
        cur = cur->next;         //指针后移
    }
    cur = head;                  //复原头指针
    //遍历剩下的链表
    while(double_p != NULL){                    //判断当前指针不为NULL
        double_p_next =  double_p->next;       //移动指针 (反转链表需要三次指针的移动,一次指针方向的指向,这是双指针迭代)
        double_p->next  = double_p_pre;        //指针指向
        double_p_pre =  double_p;              //移动前一个指针
        double_p = double_p_next;              //移动指针 
    }
    while (cur) {                              //遍历进行比较,这里以前一个链表为判断条件,后面链表的长度比前面的长(多一个对称地方的元素,奇元素的时候),要么就是相等,可以用这个作为条件
        if ( cur->val != double_p_pre->val) {
            return false;
        }
        cur = cur->next;                          //指针移动到下一个位置
        double_p_pre = double_p_pre->next;        //指针移动到下一个位置
    }
    return true;
}

2.2 直接用数组的方式判断,用数组接收链表

bool isPail(struct ListNode* head) {
if(head==NULL) return true;
    int list[100000]={0};
    int num=0;
    struct ListNode* p = head;
    while(p!=NULL) {
        list[num++] = p->val;
        p=p->next;
    }
    for(int i=0; i<num/2; i++) {
        if(list[i] != list[num-1-i]) return false;
    }
    return true;
  }

2.3 小结

快慢指针查找链表中点:这种方法利用了两个指针以不同的速度遍历链表,最终当快指针到达链表末尾时,慢指针会正好处于链表中点位置。(可以用这种方法查找链表中点位置)

  1.初始化两个指针,一个称为慢指针(slow),一个称为快指针(fast)。初始时,它们都指向链表的头节点。
  2.使用循环,每次循环中慢指针向后移动一步,而快指针向后移动两步。这样,快指针的移动速度是慢指针的两倍。
  3.检查快指针是否到达链表末尾(即快指针的下一个节点为空,或者快指针本身为空)。如果是这样,循环结束,慢指针所指的节点就是链表的中点。
  这种方法的关键在于,当链表的节点数为奇数时,快指针会正好到达末尾节点,而慢指针会停在中点节点上。当节点数为偶数时,快指针会到达倒数第二个节点,慢指针会停在中间的两个节点的第一个。

struct ListNode* findMiddle(struct ListNode* head) {
    struct ListNode* slow = head;
    struct ListNode* fast = head;
    while (fast != NULL && fast->next != NULL) {
        slow = slow->next;
        fast = fast->next->next;
    }
    return slow;
}

3.BM15 删除有序链表中重复的元素

   要求:删除给出链表中的重复元素(链表中元素从小到大有序),使链表中的所有元素都只出现一次。

输入:{1,1,2}
返回值:{1,2}
输入:{1,1,2,3,4,4,4,6,7,7,7,9,9}
返回值:{1,2,3,4,6,7,9}

3.1 自己的整体思路

  1. 如果链表是空,或者只有一个元素,直接返回该链表。
  2. 链表两个元素以上,准备两个相邻指针,依次判断两指针的值是否相等,如果相等,前一个指针不动,让前一个指针指向后一个指针的下一个结点位置,把后一个指针移动到下一个结点;如果不同,同时移动两指针;然后继续判断,两指针的值,直到遍历完该链表。
    具体代码如下:
#include 
 #include 
struct ListNode* deleteDuplicates(struct ListNode* head)
{
    //删除链表中的重复元素,链表中元素从小到大有序
    struct ListNode* cur = head;               //定义一个指针,指向head
    struct ListNode* next_1 = head->next;      //头结点下一个结点
    if (cur->next == NULL || cur == NULL) {         //一个元素,或者没有元素
        return cur;
    }
    while(next_1!=NULL){                           //两个及以上的元素
        if (cur->val == next_1->val){  
            cur->next = next_1->next;              //当前结点指向下一个结点 
            next_1 = next_1->next;                 //移动下一个结点
        }else{
             cur = cur->next;                      //移动当前的指针
             next_1 = next_1->next;                //移动后一个指针
        }
    }
    return head;
}

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