链表删除所有的x元素

  • Post author:
  • Post category:其他


试图编写一个链表,实现插入后,试着编写一下删除操作。(这种使用数组的方式可能会浪费内存,但是请暂时忽略这点)

作为练习的判断,请输出删除链表内所有元素x后的序列。数组保证删除后仍不为空。

题目来源:

https://www.dotcpp.com/oj/problem2024.html


输入

第一行包括一个数字n(n<100000),表示链表内元素的个数。

接下来一行,共n个整数,表示链表内的数据

接下来一个数字x,表示要删除的元素。

输出

一行,表示删除后的序列。

样例输入

8

2 3 4 4 4 5 3 8

4

样例输出

2 3 5 3 8

这里主要讲解一下将所有重复的x元素删除的代码:

过程分析:

在这里插入图片描述

Number * delete(int num,Number *head){
  Number *current,*pre;
  current = head;
  pre = NULL;
  if(current == NULL){
    printf("error");//空链表
    exit(0);
  }else{
    while(current != NULL){
        if(current->num == num){
            //如果当前的节点是要删除的节点,那么就判断删除的节点是否为头结点
            if(pre == NULL){
            //如果删除的节点是头结点,那么原来链表的第二个节点就是头节点,此时pre依旧是NULL
                head = current->next;
            }else{
            //删除的节点不是头结点,那么pre的下一个节点就是当前节点的下一个节点
                pre->next = current->next;
            }
        }else pre = current;  //这一步需要注意的,不可以没有else,直接就写pre = current
        current = current->next;
    }
  }
  return head;
}


正确的完整

的代码:

#include<stdio.h>
#include<stdlib.h>
typedef struct NUMBER{
    int num;
    struct NUMBER *next;
}Number;

//创造链表
Number * create(int n){
   Number *head,*current,*node;
   int i;
   head = NULL;
   for(i = 1; i <= n; i++){
     node = (Number *)malloc(sizeof(Number));
     scanf("%d",&node->num);
     node->next = NULL;
     if(i == 1) head = node;
     else current->next = node;
     current = node;
   }
   return head;
}
Number * delete(int num,Number *head){
  Number *current,*pre;
  current = head;
  pre = NULL;
  if(current == NULL){
    printf("error");//空链表
    exit(0);
  }else{
    while(current != NULL){
        if(current->num == num){
            //如果当前的节点是要删除的节点,那么就判断删除的节点是否为头结点
            if(pre == NULL){
            //如果删除的节点是头结点,那么原来链表的第二个节点就是头节点,此时pre依旧是NULL
                head = current->next;
            }else{
            //删除的节点不是头结点,那么pre的下一个节点就是当前节点的下一个节点
                pre->next = current->next;
            }
        }else pre = current;  //这一步需要注意的,不可以没有else,直接就写pre = current
        current = current->next;
    }
  }
  return head;
}
//遍历链表
void display(Number *head){
   Number *current;
   current = head;
   if(current == NULL){
    printf("error");//空链表的情况
    exit(0);
   }else{
     while(current != NULL){
        printf("%d ",current->num);
        current = current->next;
     }
     printf("\n");
   }
}
int main(){
  int n,num;
  Number *head;
  scanf("%d",&n);//输入节点的个数
  head = create(n);//创造n个节点的链表
  scanf("%d",&num);//输入要删除的元素
  head = delete(num,head);
  display(head);
  return 0;
}

运行结果:

在这里插入图片描述

在这里插入图片描述

类似的题目:

给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。

示例 1:

输入:head = [1,2,6,3,4,5,6], val = 6

输出:[1,2,3,4,5]

示例 2:

输入:head = [], val = 1

输出:[]

示例 3:

输入:head = [7,7,7,7], val = 7

输出:[]

来源:力扣(LeetCode)

链接:

https://leetcode-cn.com/problems/remove-linked-list-elements


对应的代码:

class Solution {
    public ListNode removeElements(ListNode head, int val) {
        ListNode newHead = new ListNode(0);
        newHead.next = head;
        ListNode cur = newHead,pre = newHead;
        while(cur != null){
        /*
        这一步必须放在这里,也正因如此,一开始的赋值为newHead,
        因为如果在下面查找val的节点的时候,由于cur是null而退出循环的
        时候,那么cur.next会发生报错,因此这一步需要放在找第一个不为val的
        节点的代码前面
        */
           cur = cur.next;   
           while(cur != null && cur.val == val) //找到第一个不等于val的节点
              cur = cur.next;
           pre.next = cur;
           pre = pre.next; 
        }
        return newHead.next;
    }

}

运行结果:

在这里插入图片描述

看到这里,有小伙伴会问了,那我如果

在删除重复的x元素的代码中,将pre = current直接写,不需要else,会造成什么样的结果呢

Number * delete(int num,Number *head){
  Number *current,*pre;
  current = head;
  pre = NULL;
  if(current == NULL){
    printf("error");//空链表
    exit(0);
  }else{
    while(current != NULL){
        if(current->num == num){
            //如果当前的节点是要删除的节点,那么就判断删除的节点是否为头结点
            if(pre == NULL){
            //如果删除的节点是头结点,那么原来链表的第二个节点就是头节点,此时pre依旧是NULL
                head = current->next;
            }else{
            //删除的节点不是头结点,那么pre的下一个节点就是当前节点的下一个节点
                pre->next = current->next;
            }
        }
        pre = current;  //这一步没有else,直接就写pre = current
        current = current->next;
    }
  }
  return head;
}

请看过程分析:

在这里插入图片描述

从上面的分析中可以知道,如果没有else,即无论当前的节点是否为要删除的节点,都有进行pre = current的操作的话,那么就会导致没有将x删除全部,导致错误,不符合题目的要求了。


错误的

代码:

#include<stdio.h>
#include<stdlib.h>
typedef struct NUMBER{
    int num;
    struct NUMBER *next;
}Number;
Number * create(int n){
   Number *head,*current,*node;
   int i;
   head = NULL;
   for(i = 1; i <= n; i++){
     node = (Number *)malloc(sizeof(Number));
     scanf("%d",&node->num);
     node->next = NULL;
     if(i == 1) head = node;
     else current->next = node;
     current = node;
   }
   return head;
}
Number * delete(int num,Number *head){
  Number *current,*pre;
  current = head;
  pre = NULL;
  if(current == NULL){
    printf("error");
    exit(0);
  }else{
    while(current != NULL){
        if(current->num == num){
            //如果当前的节点是要删除的节点,那么就判断删除的节点是否为头结点
            if(pre == NULL){
                head = current->next;
            }else{
                pre->next = current->next;
            }
        }
        pre = current;
        current = current->next;
    }
  }
  return head;
}
void display(Number *head){
   Number *current;
   current = head;
   if(current == NULL){
    printf("error");
    exit(0);
   }else{
     while(current != NULL){
        printf("%d ",current->num);
        current = current->next;
     }
     printf("\n");
   }
}
int main(){
  int n,num;
  Number *head;
  scanf("%d",&n);
  head = create(n);
  scanf("%d",&num);
  head = delete(num,head);
  display(head);
  return 0;
}

运行结果:

在这里插入图片描述

在这里插入图片描述

但是如果题目要求是删除所有的重复数字怎么办呢?这里插入一道算法题哈:

给定一个

排序链表,删除所有含有重复数字的节点,只保留原始链表中没有重复出现

的数字。

示例 1:

输入: 1->2->3->3->4->4->5

输出: 1->2->5

示例 2:

输入: 1->1->1->2->3

输出: 2->3

来源:力扣(LeetCode)

链接:

https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list-ii/


通过观察题意,题目明确说明了,这个链表是已经排好序了的,那么这时候,我们要

判断当前这个节点是否已经重复,那么只需要判断这个节点的后一个节点的值和他是否相同就好了。然而真的是这样吗?如果这个节点是所有重复数字的最后一个数字呢?如果这个节点是最后一个节点的情况我们是否已经考虑过了呢?所以再上面思路的前提下,我们还需要考虑这些情况的,否则到时候就会发生意想不到的错误哦

解题思路:

准备工作:定义一个boolean类型的(这是再java中的)变量,表示这个节点前面是否已经出现过了flag,如果是在C语言中,则定义一个int类型的变量,如果为真,那么表明这个节点前面已经出现过和他重复的节点,否则这个节点的前面没有出现过和他重复的节点

1、通过定义一个假节点pre,使得这个假节点接在链表的头结点的前面。(即此时这个链表是一个带有假节点的链表了),便于删除吧,

因为如果重复数字在头结点,那么如果没有这一步的话,那么操作相对会麻烦些,当然这是我的个人看法,如果有不同意见的,请多指教哈



2、同时

定义一个临时节点t,指向这个假节点pre

,因为后面我们需要让这个pre不断地后移,此时pre.next就不再是我们想要不重复数字链表的头结点了,所以需要定义临时节点t,指向pre,从而t.next就是我们最后得到链表的头结点

3、从左到右开始遍历链表,遍历链表的时候,我们需要注意的几种情况:

①如果当前节点是最后一个节点

②当前节点不是最后一个节点

这时候,这两种情况又分别包含了不同的情况:

①如果当前节点current是最后一个节点:

  • 如果当前节点current是所有重复数字的最后一个节点,例如[1,2,3,3,4,4],那么我们只需要将pre.next = current.next即可

  • 如果当前节点不是重复数字,例如[1,2,3,3,4,4,5],那么我们就将pre.next = current即可

    ②如果当前节点不是最后一个节点:

  • 如果当前节点的值不等于这个节点的下一个节点的值,即current.val != current.next.val,此时我们需要注意了,此时当前节点可能使重复数字的最后一个节点,这时候我们需要通过上面定义的flag变量来判断当前节点的前面是否已经出现过了和他重复一样的节点,如果没有,那么就将进行pre.next = current; pre = current的操作,否则,如果有,那么让flag取反。

  • 如果当前节点的值等于这个节点的下一个节点的值,那么我们就让上面定义的flag变量为真即可。

    对应的代码(java实现的):

class Solution {
    public ListNode deleteDuplicates(ListNode head) {
       if(head == null || head.next == null)
           return head;//如果链表为空或者只有一个链表的时候,那么就返回这个节点
       ListNode pre = new ListNode(0);
       ListNode t = pre;//定义一个临时变量,指向pre这个节点的地址
       pre.next = head;//定义一个带有假节点的链表,到最后只要返回pre.next即可
       ListNode current = head;
       boolean flag= false;//表示当前这个节点没有出现过
       while(current != null){
        if(current.next == null){
            /*
            如果当前这个节点是最后一个数那么需要分两种情况进行讨论
            最后这个节点再前面已经出现过了,那么pre.next就直接为null
            即可,否则如果最后一个节点没有出现过,那么直接将pre.next = 
            current即可
            */
            if(flag == true)
               pre.next = null;
            else
               pre.next = current;
            break;
        }
         //如果这个节点不是最后一个节点
         if(current.val != current.next.val){
              if(!flag){
                  //如果当前这个节点前面没有出现过,那么就将pre接在这个节点的前面,之后将pre后移
                  pre.next = current;
                  pre = current;
              }
              flag = false;//这个是考虑到当前节点是最后一个重复的数字,如果没有这一步的话,那么就会导致flag一直为true,从而没有办法将不重复的数字进行拼接
           }else{
               flag = true;
           }
           current = current.next;
       }
       return t.next;//由于再遍历终pre是发生了移动的,所以不可以返回pre.next
    }
}

有伙伴会问了,那如果题目

不是排好序

的,要求你将所有的重复数字删除怎么办呢?比如1,2,4,4,3,5,4,4,8,要求将所有的重复数字删除,并且

按照出现的先后顺序遍历

,最后得到1,2,4,3,5,8?那这样又该怎样做呢?

其实我的做法是这样的:

1、定义一条新链表,表示最后遍历的链表,也就是最后的结果

2、遍历原来的链表,

将当前节点的值在新链表中进行查找,判断新链表中是否含有这个节点的值,如果含有了,那么就不用做任何操作,直接后移到下一个节点,如果没有,那么就将当前节点的值插入到新链表的尾节点的地方

,从而实现新链表中没有重复数字,同时每一个数字是按照原来链表的先后顺序进行排列的。

3、重复上述操作,直到链表遍历为止。这时候,返回新链表的头节点即可。

对应的代码(C语言):

#include<stdio.h>
#include<stdlib.h>
typedef struct NODE{
   int val;
   struct NODE *next;
}Node;
typedef Node * LinkedList;
LinkedList create(){
   int i,n;
   LinkedList head,current,node;
   /*
   如果题目要求是排序的话,有假节点的操作会方便些
   */
   head = (LinkedList)malloc(sizeof(Node));
   head->next = NULL;//创建一个假节点,从而方便链表的增、删
   current = head;
   printf("请输入链表的个数:");
   scanf("%d",&n);
   for(i = 0; i < n; i++){
      node = (LinkedList)malloc(sizeof(Node));
      scanf("%d",&(node->val));
      node->next = NULL;
      current->next = node;
      current = node;
   }
   return head;
}
void display(LinkedList head){
    LinkedList current = head->next;
    while(current != NULL){
        if(current->next == NULL)//如果是最后一个节点,那么将它的值输出之后换行
            printf("{%d}\n",current->val);
        else
            printf("{%d}->",current->val);
        current = current->next;
    }
}
LinkedList insertDestinct(LinkedList root,int val){
   LinkedList current = root;
   int flag = 0;//标记val是否已经在链表root中
   while(current->next != NULL){
        //如果当前节点的值不等于val,那么继续后移,判断是否含有val值得节点
        //如果有,那么就不用将这个值插入到新链表中,否则插入
        if(current->next->val == val){
            flag = 1;
            break;
        }
        current = current->next;
   }
   if(!flag){
      //如果没有在root链表中没有找到val,那么将其插入
      LinkedList node = (LinkedList)malloc(sizeof(Node));
      node->val = val;
      node->next = NULL;
      /*
      由于最后遍历结束得时候,current是最后一个节点,所以直接执行current->next = node
      从而实现了新节点插入到链表尾部
      */
      current->next = node;
   }
   return root;

}
LinkedList deleteRepeate(LinkedList head){
   //遍历原来的链表
   LinkedList current = head->next,root;
   root = (LinkedList)malloc(sizeof(Node));
   root->next = NULL;//新链表的假结点
   while(current != NULL){
       root = insertDestinct(root,current->val);
       current = current->next;//
   }
   return root;
}
int main(){
  LinkedList head;
  head = create();
  display(head);
  printf("删除所有的重复数字,并按照原来顺序输出:");
  head = deleteRepeate(head);
  display(head);
  return 0;
}

运行结果:

在这里插入图片描述



版权声明:本文为weixin_46544385原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。