题目描述:
给你一个单链表的头节点head
,请你判断该链表是否为回文链表。如果是,返回true
;否则,返回false
。
示例 1:
输入:head = [1,2,2,1]
输出:true
示例 2:
输入:head = [1,2]
输出:false
提示:
链表中节点数目在范围[1, 105] 内
0 <= Node.val <= 9
进阶:
你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?
感觉这道题不难,但是自己写了一下运行后却超出时间范围了,有点找不出是什么问题,应该是反转链表时出了问题,后续会继续对齐进行完善。
思路上是:
1.新建一个节点temph,用于存储原来的头结点指针;
2.新建一个节点tempt,用于反转链表时保留中间结点;
3.反转链表,得到以tail为头结点的一个新链表;
4.head逐个向后移动,tail逐个向前移动,两两进行比较,若相等,继续比较;若不等,返回false。
代码如下:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public boolean isPalindrome(ListNode head) {//双指针
ListNode tail=head; //从头开始遍历,以找到尾节点
ListNode tempt=head;//用于保存反转链表时的中间结点
ListNode temph=head;//保留头结点的指向
while(temph!=null){//反转链表,这段代码有问题!陷入了循环
tempt=tail; //临时的尾指针保留住上一次反转之后出现的尾指针
tail=temph.next;//真实的尾指针向后移一位
tail.next=tempt;//尾指针的下一个节点指向保留下来的那个节点
temph=temph.next;//临时头指针向后遍历一位
}
while(head!=tail){
if(head.val!=tail.val){
return false;
}
head=head.next;
tail=tail.next;
}
return true;
}
}
方法一:将值复制到数组中后用双指针法
这是官网中的方法一,其思路为:
由于链表相较于数组的特殊性,链表无法直接查询某个值,而需通过遍历,且无法逆序查找,只能单向查找,而数组可以在O(1)内获取某个值,所以综合考虑使用数组列表先保存原链表中的值,再在数组列表中用双指针一头一尾同时逐个向中靠拢,逐个比较数组中的值;
注意:
由于我在平时的使用中缺乏注意数组列表的使用细节,所以有以下地方需要着重注意区分:
1.数组列表的创建不需要向数组那样有确定的长度,可以直接List<Integer> arrList=new ArrayList<Integer>();多态创建,注意别丢了第二个泛型;
2.数组列表中获取值为:arrList.get(i),而非平时链表中的linkList.val;
3.获取数组长度的方式为arrList.size(),而非普通数组中的arr.length。
执行结果:通过
执行用时:8 ms, 在所有 Java 提交中击败了40.24%的用户
内存消耗:50.9 MB, 在所有 Java 提交中击败了28.78%的用户
通过测试用例:85 / 85
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public boolean isPalindrome(ListNode head) { //将值复制到数组中后用双指针法
List<Integer> arrList=new ArrayList<Integer>();
ListNode currentNode=head;
while(currentNode!=null){ //注意是当前节点,不要习惯性使用下一个结点来判断
arrList.add(currentNode.val);
currentNode=currentNode.next;
}
int left=0;
int right=arrList.size()-1;
while(left<right){
if(!arrList.get(left).equals(arrList.get(right))){//注意数组列表获取值的方法,与.val区分
return false;
}
left++;
right--;
}
return true;
}
}
方法二:递归
这是官网中的方法二,使用递归的巧妙之处及总体思路在于:
当递归至最后一步后开始跳出递归时,正好类似链表反转后的结果,在此处进行首尾逐渐向中靠拢的比较方式就很优雅!我们在方法外部还定义了一个指向头结点的全局变量,用于与递归方法中的递归跳出值进行比较,本质上就是在进行正向和逆向迭代匹配。
详细思路:
1.递归方法中,先对结点判空,为空则表示已到达链表末端,直接返回;非空则表示未到链表末端,仍需继续递归直到找到链表末端;
2.找到末端之后,返回true,开始跳出递归,此时直接执行第二个if(因为是由此开始递归的,所以跳出递归时也是从这开始执行),if(!true)意味着不满足条件,直接开始判断值,若值一直相等(一边相等会一边跳出递归)直至最后跳出所有递归,最终返回true,即为回文;
3.若在第三个if判断值是否相等的时候出现了两值不等的情况,则在此返回false,那么跳出那次递归之后第二个if为if(!false)满足该循环,之后的所有跳出递归都在此满足条件,最终返回false,即不为回文。
执行结果:通过
执行用时:16 ms, 在所有 Java 提交中击败了7.92%的用户
内存消耗:55.4 MB, 在所有 Java 提交中击败了12.42%的用户
通过测试用例:85 / 85
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
private ListNode front; //外部全局变量,由于与递归后返回的值作比较
public boolean isPalindrome(ListNode head) { //题给方法
front=head;
return recursivelyCheck(head); //调用递归方法,传进头指针,返回得到最终值
}
public boolean recursivelyCheck(ListNode current){
if(current!=null){ //当前节点的非空
//传进下一个结点,先判断是否为空若,
//为空说明已递归至链表末端,返回true-->if(!true)-->不执行,去到下一个if
//不为空则继续递归调用直至找到最后一个结点-->去到上一个注释
if(!recursivelyCheck(current.next)){
//由此递归,也由此跳出递归,若跳出时为false(下一注释),满足此if,此后均在此满足,即该递归最终返回false,非回文
return false;
}
//判断值是否相等,若不等,返回false,并逐步跳出递归,最后返回false
if(front.val!=current.val){
return false;
}
front=front.next;
}
return true;
}
}
方法三:快慢指针
这是官网中的方法三:
1.平分链表:该方法利用慢指针一次走一个结点,快指针一次走两个结点,当快指针的下一个或下下个指向为null时,说明链表遍历完毕,此时快指针遍历到末尾,而慢指针正好位于链表中央;
2.反转并比较:将后半段链表反转后,同时从两段链表中从前往后遍历,一一比较,最后返回得到的结果;
3.恢复链表:由于通常情况下,提出比较的人并不希望自己的链表结构受到影响,所以比较后还需要将链表反转回来,只需要再次将对应段头结点传入反转链表的方法中即可。
执行结果:通过
执行用时:6 ms, 在所有 Java 提交中击败了60.32%的用户
内存消耗:48.2 MB, 在所有 Java 提交中击败了82.32%的用户
通过测试用例:85 / 85
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public boolean isPalindrome(ListNode head) {//快慢指针
if(head==null){
return true;
}
//找到前半段的尾节点
ListNode firstHalfEnd = endOfFirstHalf(head);
//获取反转后的后半段链表,从前半段的最后一个结点的下一个开始反转
ListNode secondHalfStart = reverseList(firstHalfEnd.next);
//判断是否为回文
ListNode p1 = head;
ListNode p2 = secondHalfStart;
boolean result = true;
while (result && p2 != null) {
if (p1.val != p2.val) {
result = false;
}
p1 = p1.next;
p2 = p2.next;
}
//还原链表并返回结果
firstHalfEnd.next=reverseList(secondHalfStart);//通种方法将后半段链表反转回去
return result;
}
private ListNode reverseList(ListNode head) {
ListNode prev = null;
ListNode curr = head;
while (curr != null) {
ListNode nextTemp = curr.next;
curr.next = prev;
prev = curr;
curr = nextTemp;
}
return prev;
}
//实现快慢指针,最后返回慢指针,得到前半段的最后一个结点
private ListNode endOfFirstHalf(ListNode head) {
ListNode fast = head;
ListNode slow = head;
while (fast.next != null && fast.next.next != null) {//因为fast每次走两步,所以需要多判断一点
fast = fast.next.next;
slow = slow.next;
}
return slow;
}
}
平平无奇小白程序媛一枚,欢迎各位大佬交流指教,如有不正确的地方,欢迎留言改正,谢谢!!!