栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

单链表初级OJ题【C语言】

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

单链表初级OJ题【C语言】

typedef struct ListNode 
{
	int val;
	struct ListNode* next;
}ListNode;     //节点

void PrintList(ListNode* head)
{
	while (head!=NULL) {
		printf("%d->",head->val);
		head = head->next;
	}
	printf("NULLn");
}    //打印每个节点数据

一、删除节点

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

         

思路1:直接遇到节点后,把节点前后相连接,把节点让出来释放掉。

struct ListNode* removeElements(struct ListNode* head, int val) {
    struct ListNode* p = head;
    struct ListNode* pre = NULL;

    while (p) {
        if (val == p->val) {
            if (p == head) {
                head = head->next;
                free(p);
                p = head;
            }
            else {
                pre->next = p->next;
                free(p);
                p = pre->next;
            }
        }
        else {
            pre = p;
            p = p->next;
        }
    }
    return head;
}

PS:因为要让节点前后连接,所以注意要保留前一个节点的指针。

        如果是头节点删除,那么不用连接前后节点,直接把链表头节点的下一个节点当成新的头节点,然后删除原来的节点。

        如果节点不是要删除的节点,让指针往下走继续找。

测试:

void test1()
{
	ListNode* p1 = (ListNode*)malloc(sizeof(ListNode));
	p1->val = 1;
	ListNode* p2 = (ListNode*)malloc(sizeof(ListNode));
	p2->val = 2;
	ListNode* p3 = (ListNode*)malloc(sizeof(ListNode));
	p3->val = 3;
	ListNode* p4 = (ListNode*)malloc(sizeof(ListNode));
	p4->val = 4;
	ListNode* p5 = (ListNode*)malloc(sizeof(ListNode));
	p5->val = 5;
	p1->next = p2;
	p2->next = p3;
	p3->next = p4;
	p4->next = p5;
	p5->next = NULL;
	PrintList(p1);
	p1 = removeElements(p1,4);
	PrintList(p1);
}

int main()
{
	test1();
	return 0;
}

 思路2:如果不是要查找的节点把节点拿下来,头插入另一个链表。

 

struct ListNode* removeElements(struct ListNode* head, int val){
   
   struct ListNode* tail=NULL;
   struct ListNode* p=head;
   head=NULL;
   while(p){
       if(p->val==val){
           struct ListNode* del=p;
           p=p->next;
           free(del);
       }
       else{
           if(tail==NULL){
               tail=p;
               p=p->next;
               head=tail;
           }
           else{
               tail->next=p;
               tail=tail->next;
               p=p->next;
           }
       }
   }

   if(tail!=NULL)
        tail->next=NULL;
   return head;
}

PS:刚开始另一个链表为NULL,第一个拿下来的节点就为头节点。全部插入完成后要让尾节点指向NULL。

        这还要定义一个指针用来保存另一个链表的尾结点,这样每次插入节点就不用遍历尾节点了。

        如果给到是空链表,那么tail指针这里是不能让其Next指针为NULL的,所以这里要判断一下。 

二、反转一个单链表

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

 思路1:直接把遇到的节点拿下来头插进另一个链表。

 插入完就会发现链表已经被翻转过来了

struct ListNode* reverseList(struct ListNode* head) {
    struct ListNode* p = head;
    struct ListNode* h = NULL;
    struct ListNode* pre = NULL;
    while (p) {
        pre = p;
        p = p->next;
        pre->next = h;
        h = pre;
    }
    return h;
}

测试:

思路2:设三个指针,一个指向要逆转指针节点的下一个,一个指向另一个链表的尾结点,一个指向当前链表的节点。当现在链表的节点插入到另一个链表后,把下一个节点指针赋值给它继续插入。

ListNode* rL_2(ListNode* head)
{
    if (!head)
        return head;

    struct ListNode* p = head;
    struct ListNode* pre = NULL;
    struct ListNode* next = p->next;

    while (p) {
        p->next = pre; 
        pre = p;
        p = next;
        if (next != NULL)
            next = next->next; //防止越界
    }

    return pre;
}

        刚开始pre指向NULL,第一个插入的节点相当于链表最后的尾节点。

PS:其实会发现这个方法也相当于头插法,只不过把指针指向的下一个节点提前保存了,赋值的顺序也放后面了。

         

        赋值到最后的时候next此时为NULL,再赋值给p指针,再往后走一次,next会越界。所以这里只能用p是否为空来判断循环结束并且要判断next是否已为NULL,如果为NULL就不让next往下走了。 

三、返回链表的中间结点 

 给定一个头结点为 head 的非空单链表,返回链表的中间结点。

思路:快慢指针,一个指针走一个节点,一个指针走两个节点,那么第二个指针走到尾结点,那么前一个指针就刚好在链表中间的节点。

struct ListNode* middleNode(struct ListNode* head){
    struct ListNode* p1=head;
    struct ListNode* p2=head;

    while( p2 && p2->next){
        p1=p1->next;
        p2=p2->next->next;
    }
    return p1;
}

        因为要存在链表长度是奇数偶数的情况,也就是说p2指针有可能走到尾结点,或者走到尾结点的下一个NULL节点。所以判断时都要判断一个,当p2为空或者p2下一个节点为空,说明循环结束。 

测试:

四、链表中倒数第k个结点

输入一个链表,输出该链表中倒数第k个结点。 

思路:快慢指针,第二个指针比第一个指针长k,那么第二个指针到链表结束,第一个指针就是倒数第k个节点。

struct ListNode* FindKthToTail(struct ListNode* pListHead, int k ) {
 
    if(!pListHead || k<=0)
            return NULL;

    struct ListNode* p1=pListHead;
    struct ListNode* p2=pListHead;
    
    while(k--){ 
        if(p2)
            p2 = p2->next;
        else
            return NULL; 
    }
    
    while(p2){
        p1=p1->next;
        p2=p2->next;
    }
    return p1;
}

        如果给到链表为空或者k为负数,返回NULL指针。

        当k刚好等于链表长度时p2指向NULL,但是先判断的k==0,所有不会返回NULL,这时候,p1刚好就是链表头节点。 

        如果k大于链表长度,那么p2越界继续往后走会越界,所以判断p2为空时,返回NULL

测试:


拓展:哨兵位(虚设节点)

        这里以第一题,删除节点来说,会发现如果删除头节点的时候会多判断一次。在链表前面设置一个哨兵位,可以减少判断和操作。

        

        哨兵位不存储数据,链表操作从哨兵节点下一个节点开始,那么就会简化所以要判断头节点的情况。        

        返回的时候返回哨兵节点的下一个即可,当然要记得释放掉哨兵节点

struct ListNode* removeElements(struct ListNode* head, int val){

   struct ListNode* p=head;
   struct ListNode* tail=(struct ListNode*)malloc(sizeof(struct ListNode));

   head=tail;
   tail->next=NULL;

   while(p){

       if(p->val==val){
           struct ListNode*del=p;
           p=p->next;
           free(del);
       }
       else{
           tail->next=p;
           tail=p;
           p=p->next;
       }
   }

   tail->next = NULL;//尾节点记得置空

   struct ListNode* re=head;
   head=head->next;
   free(re);

   return head;

}

         

 

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/854151.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号