单链表的经典oj题(1)

前言

这次博客将要以图解的形式,把单链表的经典题目,讲解,绝对是干货,来吧兄弟萌

第一题

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

示例 1:

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

题目链接

203. 移除链表元素 - 力扣(LeetCode)

直白的思路

这个题目完全可以无脑解决,看嘛,我们可以直接再重新构建一个链表,把不等于val值的结点

尾插起来不就好了嘛

好吧这个也让大家看看代码,我们以上面例子为例

这个代码有点长

#include<stdio.h>
#include<stdlib.h>
struct SListNode {
	int val;
	struct SListNode* next;
};
//创建节点
struct SListNode* Buynewnode(int input)
{
	struct SListNode* newnode = (struct SListNode*)malloc(sizeof(struct SListNode));
	newnode->next = NULL;
	newnode->val = input;
	return newnode;
}
int main()
{
	//1->2->6->3->4->5->6
	//构建链表
	struct SListNode* head = Buynewnode(1);
	head->next = Buynewnode(2);
	head->next->next = Buynewnode(6);
	head->next->next->next=Buynewnode(3);
	struct  SListNode* head4 = head->next->next->next;
	head4->next=Buynewnode(4);
	head4->next->next= Buynewnode(5);
	head4->next->next->next=Buynewnode(6);
	int val = 6;
	//遍历链表
	struct SListNode* newtail = NULL;
	struct SListNode* newhead = NULL;
	while (head)
	{
		if (head->val!= val)
		{
			if (newtail == NULL)
			{
				newtail=newhead = Buynewnode(head->val);
			}
			else
			{
				newtail->next = Buynewnode(head->val);
				newtail = newtail->next;
			}
		}
		head = head->next;
	}
	while (newhead)
	{
		printf("%d ", newhead->val);
		newhead = newhead->next;
	}
	return 0;
}

看看结果

是不是对上了,当然我们这里是自己实现的,所以代码量就多了些

改进思路:双指针+哨兵位节点

我们可不可以不构建,在原有的链表上改变呢

可以

通过前后指针可以抵达 前后中三个节点,如果要删除一个结点就把中间的结点删除

让前节点连接后节点

看图吧

ok

再次看看代码

struct ListNode* removeElements(struct ListNode* head, int val) {
    struct ListNode*phead=(struct ListNode*)malloc(sizeof(struct ListNode));
    phead->next=head;
    struct ListNode*cur=head;
    struct ListNode*prev=phead;
    while(cur)
    {
        if(cur->val==val)
        {
           struct ListNode*temp=cur->next;
           free(cur);
           cur=temp;
           prev->next=cur;
        }
        else
        {
            prev=cur;
            cur=cur->next;
        }
    }
    return phead->next;
}

这里直接用一个函数,来表示好了,主要优化空间复杂

第二题

反转链表

看看题目吧

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

示例 1:

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

206. 反转链表 - 力扣(LeetCode)

有没有想到暴力的解法呢

其实就是我们仍然可以重新构建一个链表,遍历旧的链表,在用头插的方式,刚好反转了链表对吧

1->2->3->4->5

第一次头插1 第二次头插 2->1 第三次头插 3->2->1 第四次头插 4->3->2->1 第五次头插

5->4->3->2->1

对吧

看代码就好了,代码以上面为例子

#include<stdio.h>
#include<stdlib.h>
struct SListNode {
	int val;
	struct SListNode* next;
};
struct SListNode* Buynewnode(int input)
{
	struct SListNode* newnode = (struct SListNode*)malloc(sizeof(struct SListNode));
	newnode->next = NULL;
	newnode->val = input;
	return newnode;
}
int main()
{
	//构建链表
	struct SListNode* head = Buynewnode(1);
	head->next = Buynewnode(2);
	head->next->next = Buynewnode(3);
	head->next->next->next = Buynewnode(4);
	head->next->next->next->next = Buynewnode(5);
	//遍历链表,并且头插
	struct SListNode* newhead = NULL;
	while (head)
	{
		if (newhead == NULL)
		{
			newhead = Buynewnode(head->val);
		}
		else
		{
			struct SListNode* newnode = Buynewnode(head->val);
			newnode->next = newhead;
			newhead = newnode;
		}
		head = head->next;
	}
	while (newhead)
	{
		printf("%d ", newhead->val);
		newhead = newhead->next;
	}
	return 0;
}

这里的时间复杂度还是o(n)但是多了空间复杂(N)

我们来看看,优化方案

双指针法

思路

第一个指针叫做 prev=NULL;第二个指针叫做 cur=head;

首先一个指针在最左侧此时为NULL 另一个指针在链表第一个元素 

首先记录第二个指针的next    temp=cur->next;

再让第二个指针指向第一个指针之后     cur->next=prev;

第一个指针继承第二个指针的值,第二个指针等于它的next  prev=next;cur=cur->next;

本质的思路就是让他们的指向相反,同时就完成了反转,红色部分为核心代码

当然,整个反转链表是一个循环,结束条件就是cur为null时,因为在它为空前

它把最后一个节点个反转了

但是要注意一点,最后我们的头结点就是prev了

看代码吧

注意这里的代码是没有将自定义函数包含,当然,看了前面的,应该就可以理解

int main()
{
	//构建链表
	struct SListNode* head = Buynewnode(1);
	head->next = Buynewnode(2);
	head->next->next = Buynewnode(3);
	head->next->next->next = Buynewnode(4);
	head->next->next->next->next = Buynewnode(5);
	//遍历链表,并且头插
	struct SListNode* prev = NULL;
	struct SListNode* cur = head;
	while (cur)
	{
		//构建临时变量的原因是:cur一旦改变方向就无法找到下一个,所以记录下一个
		struct SListNode* temp = cur->next;
		cur->next = prev;
		prev = cur;
		cur = temp;
	}
	while (prev)
	{
		printf("%d ", prev->val);
		prev = prev->next;
	}
	return 0;
}

我们看看结果吧

OK

看第三题

第三题

给你单链表的头结点 head ,请你找出并返回链表的中间结点。

如果有两个中间结点,则返回第二个中间结点。

示例 1:

输入:head = [1,2,3,4,5]
输出:[3,4,5]
解释:链表只有一个中间结点,值为 3 

示例 2:

输入:head = [1,2,3,4,5,6]
输出:[4,5,6]
解释:该链表有两个中间结点,值分别为 3 和 4 ,返回第二个结点。

提示:

  • 链表的结点数范围是 [1, 100]
  • 1 <= Node.val <= 100

这个题目更偏向于理解,怎么说呢其实有两种情况

一种是链表长度是奇数,另一种链表长度是偶数

奇数中间节点在中间

偶数的话在偏右边 

刚好对应两例子中的两种请况

主要的思路为快慢指针

还是画图吧

当然,这里仍然是要循环 结束条件有两个,一个是fast==NULL 另一个是fast->next==NULL

ok看代码吧

int main()
{
	//构造链表
	struct SListNode* head = Buynewnode(1);
	head->next = Buynewnode(2);
	head->next->next = Buynewnode(3);
	head->next->next->next = Buynewnode(4);
	head->next->next->next->next = Buynewnode(5);
	struct SListNode* slow = head;
	struct SListNode* fast = head;
	while (fast && fast->next)
	{
		slow = slow->next;
		fast = fast->next->next;
	}
	printf("%d ", slow->val);
	return 0;
}

OK,如果理解,其实没有太大的问题

第四题

实现一种算法,找出单向链表中倒数第 k 个节点。返回该节点的值

注意:本题相对原题稍作改动

示例:

输入: 1->2->3->4->5 和 k = 2
输出: 4

说明:

给定的 k 保证是有效的

暴力思路

其实暴力思路也非常简单

我们要返回的值是int类型的值,我们完全可以把整个链表遍历一遍

把它的值存入一个数组中,然后根据k返回一个值不就行了吗?

当然这里的链表的长度不确定,所以还需要动态开辟数组

思路简单,代码也很简单

void checkmemory(int* arr, int size, int capacity)
{
	if (size == capacity)
	{
		int* temp=(int *)realloc(arr, sizeof(capacity * 2));
		if (temp!= NULL)
		{
			arr = temp;
			capacity *= 2;
		}
	}
}
int main()
{
	//创建数组
	struct SListNode* head = Buynewnode(1);
	head->next = Buynewnode(2);
	head->next->next = Buynewnode(3);
	head->next->next->next = Buynewnode(4);
	head->next->next->next->next = Buynewnode(5);
	int k = 2;
	int* arr=(int *)malloc(sizeof(int)*3);
	int size = 0;
	int capacity = 3;
	while (head)
	{
		checkmemory(arr,size,capacity);
		arr[size++] = head->val;
		head = head->next;
	}
	printf("%d ", arr[size - k]);
	return 0;
}

这里仍然是空间复杂度高了

继续看优化吧

此次优化的思路来自于数学

相对位置

他不是要找倒数第k和元素吗

说明倒数第k个元素,与最后一个元素的下一个,也就是空指针相差了k个元素

比如

1->2->3->4->5->6->null

k=2,也就是5刚好与null相差2

那么反过来想,先让一个front指针走k步

然后让第二个back指针,与front指针一次走一步

那么当front指针走到NULL时,back指针与他相差k步

刚好就是倒数第k个元素

OK

这样解释应该非常清楚

看代码

//返回倒数第k个节点
int main()
{
	//创建数组
	struct SListNode* head = Buynewnode(1);
	head->next = Buynewnode(2);
	head->next->next = Buynewnode(3);
	head->next->next->next = Buynewnode(4);
	head->next->next->next->next = Buynewnode(5);
	int k = 2;
	struct SListNode* front=head;
	struct SListNode* back=head;
	while (k--)
	{
		front = front->next;
	}
	while (front)
	{
		front = front->next;
		back = back->next;
	}
	printf("%d ", back->val);
	return 0;
}

这样,就可以解决问题

总结

今天就写到这里吧,四题虽然少但是思路确是很好

希望有所帮助

  • 21
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值