题目
请判断一个链表是否为回文链表。
示例 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;
}
本章完!