博客主页:大家好我叫张同学
欢迎点赞 收藏 留言 欢迎讨论!
本文由 【大家好我叫张同学】 原创,首发于 CSDN
✨精品专栏(不定时更新) 【数据结构+算法】 【做题笔记】【C语言编程学习】
☀️ 精品文章推荐
【C语言进阶学习笔记】三、字符串函数详解(1)(爆肝吐血整理,建议收藏!!!)
【C语言基础学习笔记】+【C语言进阶学习笔记】总结篇(坚持才有收获!)
前言 |
为什么要写
刷题笔记
?
写博客
的过程也是对自己刷题过程的梳理
和总结
,是一种耗时
但有效
的方法。
当自己分享的博客帮助到他人时,又会给自己带来额外的快乐和幸福。
(刷题的快乐+博客的快乐,简直是奖励翻倍,快乐翻倍
有木有QAQ)
题目内容 |
给你一个单链表的头节点
head
,请你判断该链表是否为回文链表
。
如果是
,返回true
;否则,返回false
。
原题链接(点击跳转)
思路分析 |
回文结构
从前往后数和从后往前数均相同
从前往后:1 2 2 1
从后往前:1 2 2 1
具有对称性的链表就具有回文结构
如果是单数个结点,中间的结点无需考虑,如果其他结点对称肯定是回文结构
例如:1 2 3 1 2 也是回文结构
这里借助求链表倒数第k个结点的思路。
只要链表的
第1个结点=倒数第1个结点
第2个结点=倒数第2个结点
…
一直走到中间结点为止,都相同的话,就是回文结构。否则,就不是回文结构。
循环结束的进行/终止条件有很多,因为我们事先要求出链表的长度。
所以可以通过循环的步数:
step <= 2/length
当然,也可以根据第k个和倒数第k个之间的关系:k <= length-k-1
此外,还可以通过结点指针的关系:cur->next != end || cur != end
函数实现 |
bool isPalindrome(struct ListNode* head){
struct ListNode* tail = head;
int length = 0;//求链表长度
while(tail){
tail = tail->next;
length++;
}
int k = 1;//顺数第k个,从1开始
struct ListNode* cur = head,*end;
while(k <= length-k){//倒数第k个就是顺序第length-k个
end = head;
for(int i = 0; i < length-k; i++){
end = end->next;//通过end找到倒数第k个
}
if(cur->val != end->val)//两者比较,不同就返回false
return false;
cur = cur->next;
k++;
}
return true;//所有结点均比较过,相同,返回true
}
注意:题目中给出了链表结点数量不为0
,所以空链表
不需要考虑。对于仅有一个结点的情况,程序依然能够覆盖到,所以也不需要作为一个单独的情况来处理。
通过Leetcode
的执行代码和测试示例进行预提交,发现程序可以成功通过。但是当我们正式提交的时候,就会出现超出时间限制的问题。
一旦出现超出时间限制,我们通常可以考虑两种情况
1)程序中某些循环体结束的条件不对,导致程序进入死循环
。
2)程序算法的时间复杂度
太高,没达到预期的要求,导致运行超时
。
(这时候可能有些同学会问:”妖怪吧,为什么你可以想到我却想不到呢?“张同学回答:”别问,问就是刷题刷多了,出错调试代码的次数多了,有经验了,我太难了…每天都是夜深人静刷力扣,夜静无人码代码
”,额,开个玩笑,总之就是多实践,实践出真知
,实践是认识的源泉~
)
因为程序能通过测试用例,说明程序不可能显然死循环。我们可以点开超出时间限制的测试用例看一下,然后就可以看到…一大堆…数字,也就是测试输入量 n 很大的情况。
程序的时间复杂度为O(n^2)
,空间复杂度为O(1)
。当数据量很大的时候,因为O(n^2)
的时间复杂度,程序运行的时间就需要很长,自然就无法通过测试用例。
思路分析 |
找到问题后,我们就要思考如何处理这个问题。要想优化时间复杂度,我们会想到以空间换时间
的方式。也就是先遍历一遍原链表,将其内容复制头插
到新链表
中,那么新链表的内容实际上就是原链表从后往前数的内容。然后通过比较两个链表内容是否相同,来判断是否为回文结构
我们在第一遍遍历链表复制结点的时候,还可以顺便求出链表长度,后面比较两个链表的时候,只需要比较前 2/length
个结点即可。
函数实现 |
bool isPalindrome(struct ListNode* head){
struct ListNode* cur1 = head;
struct ListNode* newhead = NULL;
int length = 0;
while(cur1){
//复制结点头插到newhead新链表中
struct ListNode* node = (struct ListNode*)malloc(sizeof(struct ListNode));
node->val = cur1->val;
if(newhead == NULL){
newhead = node;
node->next = NULL;
}
else{
node->next = newhead;
newhead = node;
}
length++;
cur1 = cur1->next;
}
//对比两个链表,判断回文结构
cur1 = head;
struct ListNode* cur2 = newhead;
int step = length/2;
while(step--){
if(cur1->val != cur2->val)
return false;
cur1 = cur1->next;
cur2 = cur2->next;
}
return true;
}
提交程序后,Leetcode
成功通过,但是我们可以看到程序的执行时间
和内存消耗
都很大,原因如下:
(1)我们实际上遍历了两遍链表,但重点是我们用malloc
开辟新结点构成新链表这个的耗时较长
。
(2)用malloc
开辟新结点组成新链表的方式还会占用
很多内存
空间,导致内存消耗较大
。
注意,新链表newhead使用完后要将新链表中的结点都释放掉,因为这种结点都是
malloc
从堆上面申请的,不释放
会导致内存泄漏
,如果开发程序使用这段代码。就会导致电脑或手机内存越用越少
,程序运行越来越慢
!
bool isPalindrome(struct ListNode* head){
struct ListNode* cur1 = head;
struct ListNode* newhead = NULL;
int length = 0;
while(cur1){
//复制结点头插到newhead新链表中
struct ListNode* node = (struct ListNode*)malloc(sizeof(struct ListNode));
node->val = cur1->val;
if(newhead == NULL){
newhead = node;
node->next = NULL;
}
else{
node->next = newhead;
newhead = node;
}
length++;
cur1 = cur1->next;
}
//对比两个链表,判断回文结构
cur1 = head;
struct ListNode* cur2 = newhead;
int step = length/2;
while(step--){
if(cur1->val != cur2->val)
return false;
cur1 = cur1->next;
cur2 = cur2->next;
}
//释放newhead链表,防止内存泄漏
cur2 = newhead;
while(cur2){
struct ListNode* next = cur2->next;
free(cur2);
cur2 = next;
}
return true;
}
上面这种方法还可以进行小小的改进,原本是复制新结点到链表中,再比较两个链表内容是否一致。其实归根结底就是比较
两个的值
是否一样。因此,我们可以将原链表中的val值
复制到一个数组中,数组的大小可以根据length
来确定。malloc
一次性开辟一个数组空间,可以减少消耗,然后再数组里面内部可以直接比较数值是否相同。(当然也可以用数组和链表比较,只是需要将链表前面结点的val
和数组后面的val
比较)
那有没有办法对其进行改进,以达到程序的运行时间很短,同时内存消耗也很小呢?
快慢指针法 |
(1)找中点
(2)反转前半部分或者后半部分
(3)对比判断是否为回文结构
(4)还原链表
反转链表部分可参考:【Leetcode刷题笔记之链表篇】206. 反转链表
算法图解 |
函数实现 |
//迭代法
struct ListNode* reverseList(struct ListNode* head,struct ListNode* middle){
if(!head)//先判断链表是否为空
return NULL;
struct ListNode* prev = NULL;
struct ListNode* cur = head;
while( cur != middle){
struct ListNode* next = cur->next;
cur->next = prev;
prev = cur;
cur = next;
}
return prev;
}
bool isPalindrome(struct ListNode* head){
struct ListNode* fast,*slow;
fast = slow = head;
while(fast && fast->next){
slow = slow->next;
fast = fast->next->next;
}
head = reverseList(head, slow);//反转前半部分
//通过fast是否为空来判断结点为单数还是偶数,确定后面比较的起点
struct ListNode* cur1 = head,*cur2 = slow;
if(fast != NULL){
cur2 = cur2->next;
}
while(cur1 && cur1 != slow){
if(cur1->val != cur2->val)
return false;
cur1 = cur1->next;
cur2 = cur2->next;
}
//还原
struct ListNode* mark = head;
head = reverseList(head,NULL);
if(mark && mark->next)
mark->next = slow;
return true;
}