141、难度简单:
要求:O(1)(即,常量)内存解决此问题
方法一:哈希表:时间复杂度:O(N) 空间复杂度:O(N)
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public boolean hasCycle(ListNode head) {
//HashSet存储的是对象,即使val相等,指针地址也不相同
Set<ListNode> seen = new HashSet<ListNode>();
while (head != null) {
//.add()不能添加重复数据,所以当出现重复数据时是false,!使其成为ture。
//说明存在重复节点,返回true
if (!seen.add(head)) {
return true;
}
head = head.next;
}
return false;
}
}
方法二:快慢指针:时间复杂度:O(N) 空间复杂度:O(1)
本方法需要读者对「Floyd 判圈算法」(又称龟兔赛跑算法)有所了解。
来源:
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/linked-list-cycle/solution/huan-xing-lian-biao-by-leetcode-solution/
public class Solution {
public boolean hasCycle(ListNode head) {
if (head == null || head.next == null) {
return false;
}
ListNode slow = head;
ListNode fast = head.next;
while (slow != fast) {
if (fast == null || fast.next == null) {
return false;
}
slow = slow.next;
fast = fast.next.next;
}
return true;
}
}
234、难度简单:
要求:用 O(n)
时间复杂度和 O(1)
空间复杂度解决此题
参考来源:
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/palindrome-linked-list/solution/hui-wen-lian-biao-by-leetcode-solution/
方法一:将值复制到数组中后用双指针法:时空复杂度:O(n)
/**
* 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 frontPointer;
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、复制链表值到数组列表中、使用双指针法判断是否为回文;
2、先遍历链表将值赋值到数组列表中、使用双指针法来检查是否为回文。我们在起点放置一个指针,在结尾放置一个指针,每一次迭代判断两个指针指向的元素是否相同,若不同,返回 false;相同则将两个指针向内移动,并继续判断,直到两个指针相遇
方法二:递归:时空复杂度O(n):该方法比方法一效果更差
为了想出使用空间复杂度为 O(1)O(1) 的算法,你可能想过使用递归来解决,但是这仍然需要 O(n)O(n) 的空间复杂度。
使用递归反向迭代节点,同时使用递归函数外的变量向前迭代,就可以判断链表是否为回文。
算法:
class Solution {
//首指针
private ListNode frontPointer;
private boolean recursivelyCheck(ListNode currentNode) {
//当前指针不为空,也就是未达到末尾节点就继续执行
if (currentNode != null) {
//开始递归,调用自身。当上一次调用返回false(前后节点不相等时)时!起到true的作用,返回false
//但是该if判断没执行一次就会调用自己一次,让当前指针一直后移,直到当前节点为末尾节点为止,上面的if判断不成立
//返回true,才正式开始走下面的if判断首尾是否相等
if (!recursivelyCheck(currentNode.next)) {
return false;
}
if (currentNode.val != frontPointer.val) {
return false;
}
//由于上一行的if判断过了,说明首尾相同,所以 前指针后移一位
frontPointer = frontPointer.next;
}
//每一次判断首尾相等成功,就返回true,继续判断下两个节点
return true;
}
//从调用该方法开始
public boolean isPalindrome(ListNode head) {
//递归外的部分,只执行一次
frontPointer = head;
//调用递归,返回最终结果
return recursivelyCheck(head);
}
}
方法三:快慢指针:时间复杂度:O(n) 空间复杂度:O(1)
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.next.next;
slow = slow.next;
}
return slow;
}
}
原理:
解题过程:
将执行用时、内存消耗极大提高的另一种写法:(缺陷:破坏了链表结构)
public boolean isPalindrome(ListNode head) {
if(head == null || head.next == null) {
return true;
}
ListNode slow = head, fast = head;
ListNode pre = head, prepre = null;
//快慢指针,slow走到一半、fast走完全程时停止循环
while(fast != null && fast.next != null) {
pre = slow;
slow = slow.next;
fast = fast.next.next;
pre.next = prepre;
prepre = pre;
}
//处理链表个数为奇数的情况
if(fast != null) {
slow = slow.next;
}
while(pre != null && slow != null) {
//pre作为前半链表反转后的头节点
if(pre.val != slow.val) {
return false;
}
pre = pre.next;
slow = slow.next;
}
return true;
}
//作者:nuan
//链接:https://leetcode-cn.com/problems/palindrome-linked-list/solution/wo-de-kuai-man-zhi-zhen-du-cong-tou-kai-shi-gan-ju/
快指针走到末尾,慢指针刚好到中间。其中慢指针将前半部分反转。然后比较
快慢指针找链表中间结点的同时,慢指针将其扫过的链表部分进行反转,再从中间对比。
pre和prepre用于反转链表。slow和fast分别用于走一半和走全程:
这里依次记录下 while(fast != null && fast.next != null) 循环中各变量的值更替
pre = slow; 0 1 同理
slow = slow.next; 1 2
fast = fast.next.next; 2 4
pre.next = prepre; 0->null 1->0->null
prepre = pre; 0->null 1->0->null