回文链表
解法一:快慢指针(推荐)
题解
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 (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.next.next;
slow = slow.next;
}
return slow;
}
}
图示
1.找到前半部分链表的尾节点
2.反转后半部分链表
详解
-
1.首先判断头结点是否为空,如果为空,则该链表为回文链表,如果不为空,继续进行下一步判断。
-
2.定义两个方法分别为 定位前半部分链表的尾节点 和 反转后半部分节点,返回值类型都为链表。
【1>.用快慢指针的到前半部分链表的尾节点,如图一,该方法需传参以 head 为头结点的链表,最后返回以 slow 为头结点的链表。 ¥firstHalfEnd¥
2>.反转链表时,需传参以 slow.next(firstHalfEnd.next) 为头结点的链表,因为 slow 节点属于前半部分链表,我们要反转的是后半部分的链表。最后返回以 prev 为头结点的反转后的后半部分链表。¥secondHalfStart¥】 -
3.判断是否回文:
1>.p1指向前半部分链表的头结点;
2>.p2指向后半部分链表反转后的头结点;
3>.开始遍历比较两节点的值是否相同(遍历终止条件: p2.next == null)
4>.返回值类型为布尔类型
循环遍历之前先定义一个布尔类型的变量 result 值为 true,
在循环体中如果前后两值不同,result = false,
之后并不是立刻返回 result 的值 而是等遍历全部结束后先还原链表再返回结果。
还原链表时,需再次调用反转方法(传入secondHalfStart),再使 firstHalfEnd 与 反转后的头结点相连。
最后 return result,如果结果为 true 即为回文链表,结果为 false则不是
解法二(递归)
题解
class Solution {
//frontPointer不是局部变量,而是一个可变的类
private ListNode frontPointer;
//currentNode记录每次调用递归函数的指向
private boolean recursivelyCheck(ListNode currentNode) {
if (currentNode != null) {
if (!recursivelyCheck(currentNode.next)) {
return false;
}
if (currentNode.val != frontPointer.val) {
return false;
}
frontPointer = frontPointer.next;
}
return true;
}
public boolean isPalindrome(ListNode head) {
frontPointer = head;
return recursivelyCheck(head);
}
}
图示
详解
-
1.首先执行函数 isPalindrome,$0 代表链表的第一个节点即 head .建立一个与 head 相同的节点 frontPointer.
-
2.在调用函数时,计算机必须在栈帧中记录它来自哪里,因为 head 和 currentNode 都是局部变量(frontPointer是一个可变的类),因此在调用recursivelyCheck 函数前,需要把 head 记录在堆栈中。
帧栈中:
【1>.mothod:在哪个函数里调用的该函数;
2>.局部变量指向的地址:指的是调用该函数之前的那个局部变量指向的地址;
如:在调用 return recursivelyCheck(head);之前的局部变量为 head,head在此之前指向的地址为 $0.把它记录在堆栈之后,再调用 recursivelyCheck(ListNode currentNode) 使 currentNode = head。在再次调用函数之前,在堆栈中记录此时currentNode 指向的地址 $0,然后再调用 recursivelyCheck(ListNode currentNode),使 currentNode = currentNode.next
3>.Line:在第几行调用的递归函数
4>.调用函数的那行代码,其中 recursivelyCheck(ListNode currentNode)为.】 -
currentNode的移动:
在执行函数之前,在帧栈中记录局部变量的信息,调用函数时,currentNode移动,在 currentNode 从前至后的移动过程中,都一直为递归过程,直到 currentNode 指向 null 时, currentNode == null 为递归终止条件,return ture,即 为ture,当 有值时,调用栈弹出,代码回到当初入栈的位置(最后一次调用函数之前),同时currentNode 指向的地址也恢复到调用函数之前的位置(即往前移一位)。此时 !recursivelyCheck(currentNode.next) 结果为false,所以不执行该 if 语句,下一步判断前后节点(frontPointer 和 currentNode )的值是否相同,如果 return true,将该值填充到栈顶的信息中心,然后再回到当初入栈的位置(移除栈顶的信息,并把currentNode指向调用函数之前的位置),以此类推,如果返回结果一直为true,移到栈底时 recursivelyCheck(head)的为true,则最后的返回结果为 true,该链表为回文链表;如果在执行过程中发现前后某一对节点的值不一样,则会return false,再次回到当初入栈时的位置(移除栈顶的信息,currentNode 指向调用函数之前的位置),这时(!recursivelyCheck(currentNode.next)) 为 true.执行if语句,同样返回false,也就是说,当返回false时,就会陷入一直返回false的循环,直到栈中的信息全部移除,此时 frontPointer将不再移动,也不再进行比较,最后移到栈底时 recursivelyCheck(head)的为false,即return false,此链表不是回文链表
难点
复杂度分析
时间复杂度:O(n)O(n),其中 nn 指的是链表的大小。
空间复杂度:O(1)O(1)。我们只会修改原本链表中节点的指向,而在堆栈上的堆栈帧不超过 O(1)O(1)。
- 递归(不推荐)
复杂度分析
时间复杂度:O(n)O(n),其中 nn 指的是链表的大小。
空间复杂度:O(n)O(n),其中 nn 指的是链表的大小。我们要理解计算机如何运行递归函数,在一个函数中调用一个函数时,计算机需要在进入被调用函数之前跟踪它在当前函数中的位置(以及任何局部变量的值),通过运行时存放在堆栈中来实现(堆栈帧)。在堆栈中存放好了数据后就可以进入被调用的函数。在完成被调用函数之后,他会弹出堆栈顶部元素,以恢复在进行函数调用之前所在的函数。在进行回文检查之前,递归函数将在堆栈中创建 nn 个堆栈帧,计算机会逐个弹出进行处理。所以在使用递归时空间复杂度要考虑堆栈的使用情况。
声明
- 作者:E.L.E
- <著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。>
- <欢迎大家评论>