Leetcode(234)——回文链表

题目

给你一个单链表的头指针 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false 。

示例 1:


输入:head = [1,2,2,1]
输出:true

示例 2:


输入:head = [1,2]
输出:false

提示:

  • 链表中结点数目在范围 [ 1 , 1 0 5 ] [1, 10^5] [1,105] 内
  • 0 <= Node.val <= 9

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

题解

方法一:将值复制到数组中后用双指针法

思路

​​  一共为两个步骤:

  • 复制链表值到数组列表中。
  • 使用双指针法判断是否为回文。

​​  第一步,我们需要遍历链表将值复制到数组中。我们用 currentNode 指向当前结点。每次迭代向数组添加 currentNode.val,并更新 currentNode = currentNode.next,当 currentNode = null 时停止循环。

​​  执行第二步的最佳方法取决于你使用的语言。在 Python 中,很容易构造一个列表的反向副本,也很容易比较两个列表。而在其他语言中,就没有那么简单。因此最好使用双指针法(双下标)来检查是否为回文。我们在起点放置一个指针,在结尾放置一个指针,每一次迭代判断两个指针指向的元素是否相同,若不同,返回 false;相同则将两个指针向内移动,并继续判断,直到两个指针相遇。

​​  在编码的过程中,注意我们比较的是结点值的大小,而不是结点本身。正确的比较方式是:node_1.val == node_2.val,而 node_1 == node_2 是错误的。

代码实现

class Solution {public:bool isPalindrome(ListNode* head) {vector<int> vals;while (head != nullptr) {vals.emplace_back(head->val);head = head->next;}for (int i = 0, j = (int)vals.size() - 1; i < j; ++i, --j) {if (vals[i] != vals[j]) {return false;}}return true;}
};

复杂度分析

时间复杂度: O ( n ) O(n) O(n),其中 n n n 指的是链表的元素个数。

  • 第一步: 遍历链表并将值复制到数组中, O ( n ) O(n) O(n)。
  • 第二步:双指针判断是否为回文,执行了 O ( n / 2 ) O(n/2) O(n/2) 次的判断,即 O ( n ) O(n) O(n)。
    总的时间复杂度: O ( 2 n ) = O ( n ) O(2n) = O(n) O(2n)=O(n)。

空间复杂度: O ( n ) O(n) O(n),其中 n n n 指的是链表的元素个数,我们使用了一个数组列表存放链表的元素值。

方法二:递归

思路

​​  为了想出使用空间复杂度为 O ( 1 ) O(1) O(1) 的算法,你可能想过使用递归来解决,但是这仍然需要 O ( n ) O(n) O(n) 的空间复杂度。

​​  递归为我们提供了一种优雅的方式来方向遍历结点。

function print_values_in_reverse(ListNode head)if head is NOT nullprint_values_in_reverse(head.next)print head.val

​​  如果使用递归反向迭代结点,同时使用递归函数外的变量向前迭代,就可以判断链表是否为回文。

算法

​​  currentNode 指针是先到尾结点,由于递归的特性再从后往前进行比较。frontPointer 是递归函数外的指针。若 currentNode.val != frontPointer.val 则返回 false。反之,frontPointer 向前移动并返回 true。

​​  算法的正确性在于递归处理结点的顺序是相反的(回顾上面打印的算法),而我们在函数外又记录了一个变量,因此从本质上,我们同时在正向和逆向迭代匹配。

​​  计算机在递归的过程中将使用堆栈的空间,这就是为什么递归并不是 O ( 1 ) O(1) O(1) 的空间复杂度。

代码实现

class Solution {ListNode* frontPointer;
public:bool recursivelyCheck(ListNode* currentNode) {if (currentNode != nullptr) {if (!recursivelyCheck(currentNode->next)) {return false;}if (currentNode->val != frontPointer->val) {return false;}frontPointer = frontPointer->next;}return true;}bool isPalindrome(ListNode* head) {frontPointer = head;return recursivelyCheck(head);}
};

复杂度分析

时间复杂度: O ( n ) O(n) O(n),其中 n n n 指的是链表的大小。
空间复杂度: O ( n ) O(n) O(n),其中 n n n 指的是链表的大小。我们要理解计算机如何运行递归函数,在一个函数中调用一个函数时,计算机需要在进入被调用函数之前跟踪它在当前函数中的位置(以及任何局部变量的值),通过运行时存放在堆栈中来实现(堆栈帧)。在堆栈中存放好了数据后就可以进入被调用的函数。在完成被调用函数之后,他会弹出堆栈顶部元素,以恢复在进行函数调用之前所在的函数。在进行回文检查之前,递归函数将在堆栈中创建 n n n 个堆栈帧,计算机会逐个弹出进行处理。所以在使用递归时空间复杂度要考虑堆栈的使用情况。

这种方法不仅使用了 O ( n ) O(n) O(n) 的空间,且比第一种方法更差,因为在许多语言中,堆栈帧的开销很大(如 Python),并且最大的运行时堆栈深度为 1000(可以增加,但是有可能导致底层解释程序内存出错)。为每个结点创建堆栈帧极大的限制了算法能够处理的最大链表大小。

方法三:快慢指针 + 翻转链表 + 边遍历边翻转(前半部分)

思路

整个流程可以分为以下四个步骤:

  • 找到前半部分链表的尾结点,在遍历的时候通过慢指针反转前半部分链表。
  • 判断是否回文。
  • 恢复链表。
  • 返回结果。

​​  步骤一我们可以计算链表结点的数量,然后遍历链表找到前半部分的尾结点。
​​  我们也可以使用 快慢指针 在一次遍历中找到:慢指针一次走一步,快指针一次走两步,快慢指针同时出发。当快指针移动到链表的末尾时,慢指针恰好到链表的中间。通过慢指针将链表分为两部分。
​​  若链表有奇数个结点,则中间的结点不参与对比。
​​  在快慢指针遍历链表的时候,也开始反转链表的前半部分。——相比先让快慢指针遍历完链表再反战后半部分的链表,这减少了遍历一半链表的时间。

​​  步骤二比较两个部分的值,当后半部分到达末尾则比较完成,可以忽略计数情况中的中间结点。

​​  步骤三再反转一次恢复链表本身。

代码实现

两个最后没有恢复链表的写法(最好还是要恢复,因为一般调用该算法的人不想别人修改自己的数据):

class Solution {public:bool isPalindrome(ListNode* head) {if(head == nullptr || head->next == nullptr) return true;ListNode* slow = head;ListNode* fast = head;ListNode* pre = head;ListNode* pre_pre = nullptr;while(fast != nullptr && fast->next != nullptr){pre = slow;slow = slow->next;fast = fast->next->next;pre->next = pre_pre;pre_pre = pre;}if(fast != nullptr) slow = slow->next;while(pre != nullptr){if(pre->val != slow->val) return false;pre = pre->next;slow = slow->next;}return true;}
};
class Solution {public:bool isPalindrome(ListNode* head) {if (head == nullptr) return true;if(head->next && !head->next->next){if(head->val == head->next->val) return true;else return false;}ListNode* second=nullptr;ListNode* first=divi_reverse(head,second);while(first && second){if(first->val != second->val) return false;first=first->next;second=second->next;}return true;}ListNode* divi_reverse(ListNode* head,ListNode*& tmp) {ListNode* fast = head;ListNode* slow = head;ListNode* second=head->next;ListNode* third=nullptr;while (fast->next != nullptr && fast->next->next != nullptr) {fast = fast->next->next;third=second->next;second->next=slow;slow=second;second=third;}tmp=third;head->next=nullptr;return fast->next==nullptr?slow->next:slow;}
};

复杂度分析

时间复杂度: O ( n ) O(n) O(n) ,其中 n n n 指的是链表的大小。
空间复杂度: O ( 1 ) O(1) O(1)

Leetcode(234)——回文链表相关推荐

  1. LeetCode 234. 回文链表(快慢指针+链表反转)

    1. 题目 请判断一个链表是否为回文链表. 示例 1: 输入: 1->2 输出: false示例 2: 输入: 1->2->2->1 输出: true进阶: 你能否用 O(n) ...

  2. 34. Leetcode 234. 回文链表 (链表-双指针)

    给你一个单链表的头节点 head ,请你判断该链表是否为回文链表.如果是,返回 true :否则,返回 false .示例 1:输入:head = [1,2,2,1] 输出:true 示例 2:输入: ...

  3. Leetcode 234 回文链表 (每日一题 20210730)

    请判断一个链表是否为回文链表.示例 1:输入: 1->2 输出: false 示例 2:输入: 1->2->2->1 输出: true题目地址:https://leetcode ...

  4. leetcode 234. 回文链表(快慢指针+链表倒置)

    请判断一个链表是否为回文链表. 示例 1: 输入: 1->2 输出: false 示例 2: 输入: 1->2->2->1 输出: true 代码 /*** Definitio ...

  5. leetcode - 234. 回文链表

    请判断一个链表是否为回文链表. 示例 1: 输入: 1->2 输出: false 示例2: 输入: 1->2->2->1 输出: true 进阶: 你能否用 O(n) 时间复杂 ...

  6. LeetCode 234 回文链表

    原题链接 解题思路:使用vector来存储链表,然后来检查其中每一个元素,是否组成回文 /*** Definition for singly-linked list.* struct ListNode ...

  7. Leetcode 234. 回文链表 解题思路及C++实现

    解题思路: 先用快慢指针找到链表的中间节点,然后将链表一分为二: 然后将后半部分链表进行翻转,用到三个指针: 接着分别遍历两个链表,逐个比较 val 值,如果出现不相等,就返回 false. /*** ...

  8. 【LeetCode】【HOT】234. 回文链表(存入数组)

    [LeetCode][HOT]234. 回文链表 文章目录 [LeetCode][HOT]234. 回文链表 package hot;import java.util.ArrayList; impor ...

  9. LeedCode篇:234. 回文链表

    234. 回文链表 题目: 解题思路: 源码: 踩坑点: 题目: 解题思路: 1.先用快慢指针找到中间节点 2.后半个链表逆置 3.然后一一比较 源码: bool isPalindrome(struc ...

  10. [234] 回文链表

    [234] 回文链表 //请判断一个链表是否为回文链表. // // 示例 1: // // 输入: 1->2 //输出: false // // 示例 2: // // 输入: 1->2 ...

最新文章

  1. drop sql语句_用于从表中删除数据SQL Drop View语句
  2. 【HDU - 1867 】A + B for you again(KMP,next数组应用)
  3. 华为升级harmonyos的机型名单,华为智慧屏HarmonyOS 1.0.1.50更新机型和方法
  4. 使用DataWorks来调度AnalyticDB任务
  5. P53:进化了8亿年的抑癌基因
  6. [JavaScript] JavaScript作用域深度解析
  7. mysql去重操作哪个最快_如何将 MySQL 去重操作优化到极致?| CSDN 博文精选
  8. javaweb中运用fileupload上传文件
  9. 临时邮箱email网址收集
  10. Java调用soap协议的webservice
  11. Unity Shader Alpha Blend 填坑记录
  12. 迟到的Meltdown/Spectre分析
  13. matlab三次样条插值代码
  14. MATLAB完美画图:改变坐标轴刻度的显示数值,常数函数的作图
  15. 地图和地理空间革命:地理学大规模开放在线课堂(MOOC)
  16. ADB logcat调试和端口占用解决办法
  17. SpringApplicationRunListener
  18. 【面试次体验】堆糖前端开发实习生
  19. IT行业常用网站与平台汇总(持续更新...)
  20. SQLAlchemy 增删改查和基础操作

热门文章

  1. 小米k40开启热点后,计算机链接不上——AP
  2. 登录wd云盘显示内部服务器错误,网络访问失败,请检查你的网络。360云盘登陆不上!...
  3. Ubuntu上安装ffmpeg
  4. 那些40岁左右的程序员都去哪了?
  5. Fastly道歉:软件漏洞导致全球大量网站宕机
  6. access窗体中再制作查询窗体_Excel订单管理系统,窗体录入查询,快捷汇总统算,一键不加班...
  7. nginx 根据请求头判断是安卓还是webq
  8. 彪马携手Modibodi(R)为女性推出经期内衣系列
  9. 萧石心:受用一生的话
  10. CSS盒子阴影,文字阴影