0
点赞
收藏
分享

微信扫一扫

【Leetcode刷题笔记之链表篇】234. 回文链表

yellowone 2022-01-15 阅读 60

😈博客主页:🐼大家好我叫张同学🐼
💖 欢迎点赞 👍 收藏 💗留言 📝 欢迎讨论! 👀
🎵本文由 【大家好我叫张同学】 原创,首发于 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;
}

在这里插入图片描述

举报

相关推荐

0 条评论