力扣 - 234、回文链表

题目

请判断一个链表是否为回文链表。

示例 1:

输入: 1->2
输出: false

示例 2:

输入: 1->2->2->1
输出: true

进阶:
你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/palindrome-linked-list

分析

数组+双指针
我们可以先遍历一遍计算出长度,然后再创建数组,再遍历一遍把节点数据放入数组中,然后在数组俩头,俩俩判断。
时间复杂度为O(n),空间复杂度O(n);

注:链表的头部不存储数据。所以在计算长度或者输出时,需要从head->next开始。

C

#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>

typedef struct node LNode;
struct node
{
	int data;
	LNode* next;
};

LNode* CreateEndLinkList()//尾插法创建单链表
{
	LNode* head = NULL;
	LNode* q = NULL;
	LNode* p = NULL;

	int size = 0;

	head = (LNode*)malloc(sizeof(LNode));//申请头节点空间

	q = head;

	if (NULL == head)
	{
		perror("申请空间失败");
		exit(EXIT_FAILURE);
	}
	head->data = 0;
	head->next = NULL;

	printf("请输入需要添加的节点个数:");
	scanf("%d", &size);
	printf("请依次输入节点的值(用空格隔开):\n");
	while (size > 0)
	{
		p = (LNode*)malloc(sizeof(LNode));//申请子节点空间

		scanf("%d", &p->data);

		p->next = NULL;//尾插入
		q->next = p;
		q = p;

		size--;
	}

	return head;
}

int LengthLinkList(LNode* head)//单链表长度
{
	int len = 0;
	LNode* p = head->next;
	while (NULL != p)
	{
		len++;
		p = p->next;
	}

	return len;
}

void DelAllLinkList(LNode* head)//释放整个表  递归
{
	if (head != NULL)
	{
		DelAllLinkList(head->next);
	}

	free(head);
}

void PrintLinkList(LNode* head)//输出链表数据
{
	LNode* p = head->next;
	while (NULL != p)
	{
		printf("%d ", p->data);
		p = p->next;
	}
}

//判断链表是否为回文链表   
//是回文返回true  不是回文返回false
bool isPalindrome(LNode* head)
{
	int mark = true;	//记录是否回文
	int len = LengthLinkList(head);

	int* n = (int*)malloc(sizeof(int) * len);
	int i = 0;

	LNode* p = head->next;

	while (NULL != p)//遍历链表 存入数组
	{
		n[i] = p->data;
		p = p->next;
		i++;
	}

	int start = 0;		//前指针
	int end = len - 1;	//后指针

	while (start < end)
	{
		if (n[start] != n[end])//一次不等直接跳出。
		{
			mark = false;
			break;
		}

		start++;
		end--;
	}

	return mark;
}

int main()
{
	LNode* head = CreateEndLinkList();

	bool ret = isPalindrome(head);

	if (ret == true)
	{
		printf("该链表是回文链表!\n");
	}
	else
	{
		printf("该链表不是回文链表!\n");
	}

	DelAllLinkList(head);
	return 0;
}

在这里插入图片描述
在这里插入图片描述

快慢指针+反转链表
让快指针一次走俩步,慢指针一次走一步。当快指针停下的时候,慢指针走到中间或者中间靠右。主要是取决于节点数量为偶数和奇数。如下图
在这里插入图片描述
上图节点数为偶数
下图节点数为奇数
在这里插入图片描述
我们就可以将找中间值这段代码实现出来,如下

LNode* fast = head;//快指针
	LNode* slow = head;//慢指针
	
	/*
	* *如果是奇数列  找最中间的
	* *如果是偶数列  找左边的中间值
	*/
	while (fast->next != NULL && fast->next->next != NULL)
	{
		fast = fast->next->next;
		slow = slow->next;
	}

接下来我们就要开始反转链表了。
在这里插入图片描述
我们发现无论是哪种情况,需要反转的链表都是从下一个节点开始。当前节点 = slow->next;
这里我们就拿偶数举例,这里我们需要三个指针前指针、当前节点指针和后指针,反转如下图。核心四步骤。(奇数也一样,读者可以自行研究)

在这里插入图片描述
在这里插入图片描述
反转后的成品图为:
在这里插入图片描述

反转代码实现如下:

LNode* front = NULL;		//前指针
	LNode* present = slow->next;//需要反转的节点  
	LNode* later = present;		//后指针
	
	
	while (present != NULL)		//反转后半部分
	{
		later = later->next;

		present->next = front;
		front = present;
		present = later;
		
	}

然后就是判断是否为回文链表,还是双指针思想,刚好从反转后的成品图中看出最后front
前指针当好指向的就是尾节点。终止条件就是后指针向前走会走到NULL。实现如下

front = head;	//前指针
	later = front;	//后指针

	/*判断回文*/
	while (later != NULL)//后指针走到空为止
	{
		if (front->data != later->data)
		{
			mark = false;
		}
		front = front->next;
		later = later->next;
	}

这时候我们的目的已经达到了。但是我们却把链表结构该修改了,所以我们还需要把链表复原。复原的思想和反转思想一样,但是前指针后后指针指向的刚好相反,在反转的时候,是后指针去探路。在复原的时候变成了前指针去探路。实现如下

/*复原链表*/
	later = NULL;	//后指针
	present = fast;	//需要反转的节点   最右边
	front = present;//前指针

	while (front != NULL)
	{
		front = front->next;

		present->next = later;
		later = present;
		present = front;
	}

然后将代码整合,
C

#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>

typedef struct node LNode;
struct node
{
	int data;
	LNode* next;
};

LNode* CreateEndLinkList()//尾插法创建单链表
{
	LNode* head = NULL;
	LNode* q = NULL;
	LNode* p = NULL;

	int size = 0;

	head = (LNode*)malloc(sizeof(LNode));//申请头节点空间

	q = head;

	if (NULL == head)
	{
		perror("申请空间失败");
		exit(EXIT_FAILURE);
	}
	head->data = 0;
	head->next = NULL;

	printf("请输入需要添加的节点个数:");
	scanf("%d", &size);
	printf("请依次输入节点的值(用空格隔开):\n");
	while (size > 0)
	{
		p = (LNode*)malloc(sizeof(LNode));//申请子节点空间

		scanf("%d", &p->data);

		p->next = NULL;//尾插入
		q->next = p;
		q = p;

		size--;
	}

	return head;
}

int LengthLinkList(LNode* head)//单链表长度
{
	int len = 0;
	LNode* p = head->next;
	while (NULL != p)
	{
		len++;
		p = p->next;
	}

	return len;
}

void DelAllLinkList(LNode* head)//释放整个表  递归
{
	if (head != NULL)
	{
		DelAllLinkList(head->next);
	}

	free(head);
}

void PrintLinkList(LNode* head)//输出链表数据
{
	LNode* p = head->next;
	while (NULL != p)
	{
		printf("%d ", p->data);
		p = p->next;
	}
}


//判断链表是否为回文链表   
//是回文返回true  不是回文返回false
bool isPalindrome(LNode* head)
{
	bool mark = true;

	LNode* fast = head;//快指针
	LNode* slow = head;//慢指针

	/*
	* *如果是奇数列  找最中间的
	* *如果是偶数列  找左边的中间值
	*/
	while (fast->next != NULL && fast->next->next != NULL)
	{
		fast = fast->next->next;
		slow = slow->next;
	}

	/*
	* *如果快指针的下一个不为空。就再走一步
	*/
	if (fast->next != NULL)
	{
		fast = fast->next;
	}


	LNode* front = NULL;		//前指针
	LNode* present = slow->next;//需要反转的节点  
	LNode* later = present;		//后指针


	while (present != NULL)		//反转后半部分
	{
		later = later->next;

		present->next = front;
		front = present;
		present = later;

	}

	front = head;	//前指针
	later = front;	//后指针

	/*判断回文*/
	while (later != NULL)//后指针走到空为止
	{
		if (front->data != later->data)
		{
			mark = false;
		}
		front = front->next;
		later = later->next;
	}


	/*复原链表*/
	later = NULL;	//后指针
	present = fast;	//需要反转的节点   最右边
	front = present;//前指针

	while (front != NULL)
	{
		front = front->next;

		present->next = later;
		later = present;
		present = front;
	}

	return mark;
}

int main()
{
	LNode* head = CreateEndLinkList();

	bool ret = isPalindrome(head);

	if (ret == true)
	{
		printf("该链表是回文链表!\n");
	}
	else
	{
		printf("该链表不是回文链表!\n");
	}

	DelAllLinkList(head);
	return 0;
}

该方法虽然过程繁琐,但是空间复杂度大大降低。时间复杂度为O(n),空间复杂度O(1);

递归+双指针
我们通过递归的思想让一个指针走到链表尾端,然后依次对比。实现思想较难,请读者自行画图理解。
核心代码单独列出来

LNode* front = NULL;	//前指针

//递归判断回文    
//有一次false就是false。只有都是true才是true
bool recursion(LNode* head)
{
	if (head != NULL)
	{
		if (recursion(head->next) == false)//上一次判断是false 就直接返回
		{
			return false;
		}
		if (head->data != front->data)		//当前俩节点值不相等
		{
			return false;
		}

		front = front->next;//前指针往后走
	}

	return true;
}

//判断链表是否为回文链表   
//是回文返回true  不是回文返回false
bool isPalindrome(LNode* head)
{
	front = head;

	return recursion(head);
}

总代码
C

#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>

typedef struct node LNode;
struct node
{
	int data;
	LNode* next;
};

LNode* CreateEndLinkList()//尾插法创建单链表
{
	LNode* head = NULL;
	LNode* q = NULL;
	LNode* p = NULL;

	int size = 0;

	head = (LNode*)malloc(sizeof(LNode));//申请头节点空间

	q = head;

	if (NULL == head)
	{
		perror("申请空间失败");
		exit(EXIT_FAILURE);
	}
	head->data = 0;
	head->next = NULL;

	printf("请输入需要添加的节点个数:");
	scanf("%d", &size);
	printf("请依次输入节点的值(用空格隔开):\n");
	while (size > 0)
	{
		p = (LNode*)malloc(sizeof(LNode));//申请子节点空间

		scanf("%d", &p->data);

		p->next = NULL;//尾插入
		q->next = p;
		q = p;

		size--;
	}

	return head;
}

int LengthLinkList(LNode* head)//单链表长度
{
	int len = 0;
	LNode* p = head->next;
	while (NULL != p)
	{
		len++;
		p = p->next;
	}

	return len;
}

void DelAllLinkList(LNode* head)//释放整个表  递归
{
	if (head != NULL)
	{
		DelAllLinkList(head->next);
	}

	free(head);
}

void PrintLinkList(LNode* head)//输出链表数据
{
	LNode* p = head->next;
	while (NULL != p)
	{
		printf("%d ", p->data);
		p = p->next;
	}
}

LNode* front = NULL;	//前指针

//递归判断回文    
//有一次false就是false。只有都是true才是true
bool recursion(LNode* head)
{
	if (head != front)
	{
		if (recursion(head->next) == false)//上一次判断是false 就直接返回
		{
			return false;
		}
		if (head->data != front->data)		//当前俩节点值不相等
		{
			return false;
		}

		front = front->next;//前指针往后走
	}

	return true;
}

//判断链表是否为回文链表   
//是回文返回true  不是回文返回false
bool isPalindrome(LNode* head)
{
	front = head;

	return recursion(head);
}

int main()
{
	LNode* head = CreateEndLinkList();

	bool ret = isPalindrome(head);

	if (ret == true)
	{
		printf("该链表是回文链表!\n");
	}
	else
	{
		printf("该链表不是回文链表!\n");
	}

	DelAllLinkList(head);
	return 0;
}

在这里插入图片描述
在这里插入图片描述

本章完!

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林夕07

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值