【数据结构】链表练习题(1)


1.移除链表元素(LeetCode203)

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

在这里插入图片描述
思路
创建一个新链表,把不等于val的结点尾插到新链表。

代码

struct ListNode 
{
	int val;
	struct ListNode* next;	
};
struct ListNode* removeElements(struct ListNode* head, int val)
{
	struct ListNode* NewHead = NULL;//创建一个新的头结点
	struct ListNode* cur = head;//用来遍历整个链表
	struct ListNode* tail = NULL;//指向新链表的最后一个结点,用来尾插数据
	while (cur)
	{
		if (cur->val == val)//相等就把这个结点释放
		{
			struct ListNode* tmp = cur->next;//储存下一个结点的地址,防止节点释放后找不到下一个结点
			free(cur);
			cur = tmp;
		}
		else
		{
			if (NewHead == NULL)//将头结点指向不等于val的第一个元素
			{
				NewHead = tail = cur;
			}
			else
			{
				tail->next = cur;//尾插新结点
				tail = tail->next;//指向新链表的最后一个结点
			}
			cur = cur->next;
		}
	}
	if (tail)//如果最后一个结点刚好是val,那就tail指向的下一个结点置为NULL
	{        //如果链表是空,也不会进行对NULL的访问
		tail->next = NULL;
	}
	return NewHead;
}

结果
在这里插入图片描述


2.链表的中间结点(LeetCode876)

给你单链表的头结点 head ,请你找出并返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。OJ链接
在这里插入图片描述
思路
常规思路就是遍历整个链表,得到链表的结点个数从而得到中间结点的下标,再遍历链表找到并返回这个结点。但在介绍一个牛逼的方法:快慢指针,慢指针每次循环走一步,快指针每次循环走两步,最终如果链表长度是奇数,快指针指向链表的最后一个结点,慢指针刚好指向中间结点,如果链表长度是偶数,快指针指向最后一个结点指向的NULL,慢指针指向两个中间结点的第二个结点。
代码

struct ListNode 
{
	int val;
	struct ListNode* next;
};
struct ListNode* middleNode(struct ListNode* head) 
{
	struct ListNode* slow = head, * fast = head;
	while (fast != NULL && fast->next != NULL)//用&&是因为只要有一个不满足就停止循环
	{
		slow = slow->next;
		fast = fast->next->next;
	}
	return slow;
}

结果
在这里插入图片描述


3.链表的倒数第k个结点(剑指offer)

输入一个链表,输出该链表中倒数第k个结点。OJ链接
在这里插入图片描述
思路
还是用快慢指针的方法。要求倒数第k个,快指针就先走k步,此时快指针和慢指针之间的距离为k步,然后和满指针每次走一步,当快指针指向NULL,慢指针和快指针的距离是k步,慢指针和最后一个结点的距离是k-1步,慢指针刚好是倒数第k个结点。
代码

struct ListNode 
{
	int val;
	struct ListNode* next;
};
struct ListNode* FindKthToTail(struct ListNode* pListHead, int k) {
    struct ListNode* slow, * fast = NULL;
    slow = fast = pListHead;
    while (k--)//fast先走k步
    {
        if (fast == NULL)//走出链表,就直接返回NULL
        {
            return NULL;
        }
        fast = fast->next;
    }
    //此时fast与slow的距离是k,当fast指向NULL,
    //slow与最后一个节点的距离就是k-1,slow就是倒数第k个
    while (fast)
    {
        slow = slow->next;
        fast = fast->next;
    }
    return slow;
}

结果
在这里插入图片描述


4.反转链表(LeetCode206)

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。OJ链接
在这里插入图片描述
思路
法1利用三个指针(n1,n2,n3)实现逆序,n2指向当前结点,n3指向下一个结点,n1指向上一个结点。如图。
在这里插入图片描述

法2创建一个新的链表,取出要逆序链表的节点头插到新链表。
在这里插入图片描述
代码

//法1
struct ListNode 
{
    int val;
    struct ListNode* next;
    
};
struct ListNode* reverseList(struct ListNode* head)
{
    if (head == NULL)//检查是否为空
    {
        return NULL;
    }
    struct ListNode* n1 = NULL,*n2 =head,*n3 =head->next;
    while (n2)//一旦n2指向NULL就停止循环
    {
        n2->next = n1;
        n1 = n2;
        n2 = n3;
        if (n3)//防止n3越界访问,一旦n3指向NULL,就不再改变
        {
            n3 = n3->next;
        }
    }
    return n1;
}
//法2
struct ListNode 
{
    int val;
    struct ListNode* next;
    
};
struct ListNode* reverseList(struct ListNode* head)
{
    struct ListNode* newHead = NULL;
    struct ListNode* cur = head;//遍历链表
    while (cur)
    {
        struct ListNode* next = cur->next;//记住下一个结点
        cur->next = newHead;//指向头结点指向的结点
        newHead = cur;
        cur = next;
    }
    return newHead;//不用检查链表为空,因为为空不进入循环,返回NULL
}

结果
在这里插入图片描述


5.合并两个有序链表(LeetCode21)

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。OJ链接在这里插入图片描述
思路
创建一个新链表,以此比较两个升序链表的结点,取小的结点尾插。如图。在这里插入图片描述
代码

struct ListNode 
{
	int val;
	struct ListNode* next;
	
};
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
{
	struct ListNode* cur1 = list1, * cur2 = list2;//分别遍历两个链表
	struct ListNode* head = NULL,*tail = head;//head是新链表的头结点,tail指向新链表的最后一个结点,方便尾插
	if (list1 == NULL)//如果有链表为空,返回另一个链表
	{
		return list2;
	}
	if (list2 == NULL)
	{
		return list1;
	}
	while (cur1 && cur2)//有一个是NULL就结束循环
	{
		if (cur1->val < cur2->val)
		{
			if (head == NULL)//考虑到第一次插入节点
			{
				head = tail = cur1;
			}
			else
			{
				tail->next = cur1;
				tail = tail->next;
				cur1 = cur1->next;//cur1还指向原来链表的下一个结点,所以不用用指针记录下一个结点
			}
		}
		else
		{
			if (head == NULL)//考虑到第一次插入节点
			{
				head = tail = cur2;
			}
			else
			{
				tail->next = cur2;
				tail = tail->next;
				cur2 = cur2->next;//cur1还指向原来链表的下一个结点,所以不用用指针记录下一个结点
			}
		}
	}
	if (cur1 == NULL)//说明list2还有结点,直接插到新链表的尾部即可
	{
		tail->next = cur2;
	}
	if (cur2 == NULL)
	{
		tail->next = cur1;
	}
	return head;
}

结果
在这里插入图片描述


6.链表分割(牛客)

现有一链表的头指针 ListNode* pHead,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头指针。OJ链接
思路
把小于x的结点插到一个链表,大于或等于x的结点插到另一个链表,然后把两个链表链接起来。
预备知识
在使用单链表时,我们通常用用一个指针指向第一个结点,作为头结点,而这道题得使用哨兵位的头结点,用一个结点来指向链表的第一个结点。什么是哨兵位的头结点?顾名思义就是一个结点,这个结点不存储数据,只指向第一个结点,至于它的作用后面会讲到,现在只需知道它是一个不存储数据的、指向第一个结点的结点
在这里插入图片描述

代码

struct ListNode {
    int val;
    struct ListNode* next;
};
struct ListNode* partition(struct ListNode* pHead, int x)
{
    struct ListNode* smallGuard = (struct ListNode*)malloc(sizeof(struct ListNode));
    struct ListNode* bigGuard = (struct ListNode*)malloc(sizeof(struct ListNode));
    smallGuard->next = NULL;
    bigGuard->next = NULL;
    struct ListNode* smallTail = smallGuard, * bigTail = bigGuard, * cur = pHead;
    while (cur)
    {
        if (cur->val < x)
        {
            smallTail->next = cur;
            smallTail = smallTail->next;
        }
        else
        {
            bigTail->next = cur;
            bigTail = bigTail->next;
        }
        cur = cur->next;
    }
    smallTail->next = bigGuard->next;//将大链表链接到另一个链表,
    bigTail->next = NULL;//同时记得将大链表的最后一个节点指向NULL,防止其指向链表的另一个节点,导致环形链表
    pHead = bigGuard->next;//将新链表的头节点赋值给phead,因为哨兵位的头节点要释放
    free(bigGuard);
    free(smallGuard);
    bigGuard = NULL;
    smallGuard = NULL;
    return pHead;
}

结果
在这里插入图片描述

哨兵位头节点的作用
在这里插入图片描述


7.链表的回文结构(牛客)

对于一个链表,请设计一个时间复杂度为O(n),额外空间复杂度为O(1)的算法,判断其是否为回文结构。给定一个链表的头指针A,请返回一个bool值,代表其是否为回文结构。保证链表长度小于等于900。
在这里插入图片描述
思路
先找到中间节点(可以用前面的函数),从中间节点开始,对后半段进行逆序(用前面的函数),最后判断前半段和后半段是否相等。
在这里插入图片描述

代码

struct ListNode {
    int val;
    struct ListNode* next;
};
struct ListNode* middleNode(struct ListNode* head) {
    struct ListNode* slow, * fast;
    slow = fast = head;
    while ((fast != NULL) && (fast->next != NULL)) {
        slow = slow->next;
        fast = fast->next->next;
    }
    return slow;
}
struct ListNode* reverseList(struct ListNode* head) {
    struct ListNode* newHead = NULL;
    struct ListNode* cur = head;
    while (cur) {
        struct ListNode* next = cur->next;
        cur->next = newHead;
        newHead = cur;
        cur = next;
    }
    return newHead;
}
bool chkPalindrome(struct ListNode* head)
{
    struct ListNode* mid = middleNode(head);
    struct ListNode* rhead = reverseList(head);
    while (rhead && head)//当有偶数个节点时,rhead先遇到NULL,当有奇数个节点时,head和rhead同时遇到NULL
    {
        if (rhead->val != head->val)
        {
            return false;
        }
    }
    return true;
}

结果
在这里插入图片描述


猜你喜欢

转载自blog.csdn.net/Zhuang_N/article/details/129288212
今日推荐