😈博客主页:🐼大家好我叫张同学🐼
💖 欢迎点赞 👍 收藏 💗留言 📝 欢迎讨论! 👀
🎵本文由 【大家好我叫张同学】 原创,首发于 CSDN 🌟🌟🌟
✨精品专栏(不定时更新) 【数据结构+算法】 【做题笔记】【C语言编程学习】
☀️ 精品文章推荐
【C语言进阶学习笔记】三、字符串函数详解(1)(爆肝吐血整理,建议收藏!!!)
【C语言基础学习笔记】+【C语言进阶学习笔记】总结篇(坚持才有收获!)
前言 |
题目内容 |
原题链接(点击跳转)
思路分析 |
回文结构
从前往后数和从后往前数均相同
具有对称性的链表就具有回文结构
如果是单数个结点,中间的结点无需考虑,如果其他结点对称肯定是回文结构
例如:1 2 3 1 2 也是回文结构
这里借助求链表倒数第k个结点的思路。
只要链表的
一直走到中间结点为止,都相同的话,就是回文结构。否则,就不是回文结构。
循环结束的进行/终止条件有很多,因为我们事先要求出链表的长度。
函数实现 |
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
开辟新结点组成新链表的方式还会占用
很多内存
空间,导致内存消耗较大
。
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;
}
那有没有办法对其进行改进,以达到程序的运行时间很短,同时内存消耗也很小呢?
快慢指针法 |
反转链表部分可参考:【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;
}