前言:
大家好,今天是LeetCode每日一题的第11天,给大家分享的是题目是环形链表,难度系数两颗星!废话不多说,先上题目!
1.1 题目要求
题目类型:环形链表
题目内容:给定一个链表,判断链表中是否有环;如果链表中有某个节点,可以通过连续跟踪next指针再次到达该节点,则链表中存在环
注意事项:如果链表中存在环,则返回true,否则,返回false。
1.2 解题方法
1.2.1 使用Set集合处理
1.解题思路
假设存在这样一个环形链表 1 -> 2 -> 3 -> 4 -> 1
-
1的next指针指向2,2的next指针指向3,3的next指针指向4,4的next指针再指向1,这种情况就说明该链表中有环存在,所以返回值为true
-
由于节点1通过跟踪next指针再次到达该节点,因此我们可以将节点1进行标记,然后结束循环
那么能使用for循环来遍历节点吗?
不能,如果使用for循环,假设当前链表为环形链表,可能会一直遍历下去,形成一个死循环
- 因此,应该使用while循环,设置一个停止条件,当再次到达该节点时,停止循环即可
2.代码实现
2-1 非环形链表测试
- 测试代码
package com.kuang.leetcode10;
import java.util.HashSet;
import java.util.Set;
/**
* @ClassName ListCycle
* @Description 环形链表
* @Author 狂奔の蜗牛rz
* @Date 2021/8/31
*/
public class ListCycle {
//主方法测试
public static void main(String[] args) {
//初始化链表结点的当前值和next指针指向值
ListNode node5 = new ListNode(5, null);
ListNode node4 = new ListNode(4, node5);
ListNode node3 = new ListNode(3, node4);
ListNode node2 = new ListNode(2, node3);
ListNode node1 = new ListNode(1, node2);
//判断hasCycle方法的返回值为true还是false
if(hasCycle1(node1)) {
//若返回值为true, 则打印如下信息
System.out.println("当前链表为环形链表!");
} else {
//若返回值为false, 则打印如下信息
System.out.println("当前链表不为环形链表!");
}
}
/**
* 使用Set集合处理环形链表
* 判断链表中是否存在环
* @param head 链表中的结点
* @return true或false 若有环则返回true, 无环则返回false
*/
public static boolean hasCycle1(ListNode head) {
//创建一个HashSet集合(Set集合无序并且不允许重复) 空间复杂度为O(n)
Set<ListNode> set = new HashSet<ListNode>();
//使用while循环, 执行条件为head节点的值不为空
while(head != null) {
//判断set集合中是否能够成功添加head节点
if(!set.add(head)) {
//若该set集合中不能添加该元素, 说明该链表存在环, 则返回值为true
return true;
}
//将当前head节点的next值赋给下一轮的head节点
head = head.next;
}
//若set集合中不能添加, 说明不存在环,返回值为false
return false;
}
//静态内部类ListNode
static class ListNode{
//当前节点值
int val;
//指向下一节点的next指针
ListNode next;
/**
* ListNode的有参构造方法
* @param val 当前节点值
* @param next 指向下一节点的next指针
*/
public ListNode(int val, ListNode next) {
//初始化val和next值
this.val = val;
this.next = next;
}
}
}
- 测试结果
结果:测试结果与预期相同!
2-2 环形链表测试
- 测试代码
package com.kuang.leetcode10;
import java.util.HashSet;
import java.util.Set;
/**
* @ClassName ListCycle
* @Description 环形链表
* @Author 狂奔の蜗牛rz
* @Date 2021/8/31
*/
public class ListCycle {
public static void main(String[] args) {
//初始化链表结点的当前值和next指针指向值
ListNode node5 = new ListNode(5, null);
ListNode node4 = new ListNode(4, node5);
ListNode node3 = new ListNode(3, node4);
ListNode node2 = new ListNode(2, node3);
ListNode node1 = new ListNode(1, node2);
//让节点5的next指针指向节点1, 形成了一个环形链表
node5.next = node1;
//判断hasCycle方法的返回值为true还是false
if(hasCycle1(node1)) {
//若返回值为true, 则打印如下信息
System.out.println("当前链表为环形链表!");
} else {
//若返回值为false, 则打印如下信息
System.out.println("当前链表不为环形链表!");
}
}
/**
* 使用Set集合处理环形链表
* 判断链表中是否存在环
* @param head 链表中的结点
* @return true或false 若有环则返回true, 无环则返回false
*/
public static boolean hasCycle1(ListNode head) {
//创建一个HashSet集合(Set集合无序并且不允许重复) 空间复杂度为O(n)
Set<ListNode> set = new HashSet<ListNode>();
//使用while循环, 执行条件为head节点的值不为空
while(head != null) {
//判断set集合中是否能够成功添加head节点
if(!set.add(head)) {
//若该set集合中不能添加该元素, 说明该链表存在环, 则返回值为true
return true;
}
//将当前head节点的next值赋给下一轮的head节点
head = head.next;
}
//若set集合中不能添加, 说明不存在环,返回值为false
return false;
}
//静态内部类ListNode
static class ListNode{
//当前节点值
int val;
//指向下一节点的next指针
ListNode next;
/**
* ListNode的有参构造方法
* @param val 当前节点值
* @param next 指向下一节点的next指针
*/
public ListNode(int val, ListNode next) {
//初始化val和next值
this.val = val;
this.next = next;
}
}
}
- 测试结果
结果:测试结果与预期相同!
3.时间和空间复杂度分析
时间复杂度分析:
最多将Set集合中所有的节点遍历一遍,所以其时间复杂度为O(n)
空间复杂度分析:
创建了新的Set集合来存储节点,所以其空间复杂度也为O(n)
1.2.2 使用双指针法
1.解题思路
使用双指针法, 即分别设置一个快指针和慢指针,
初始状态:即慢指针在前, 快指针在后
1 -> 2 -> 3 -> 4 -> 5
|----|-------------->
slow-> quick->
- 假设该链表的节点3位置存在一个环, 即节点5的next指针指向3, 某一时刻slow指针和quick指针会都进入到环中, 直至两个指针重合
1 -> 2 -> 3 -> 4 -> 5
|----|----|---->--->|
quick low
|<--------|
- 假设该链表不存在环,那么quick指针会一直移动到最后, 直至其值为null
2.代码实现
2-1 非环形链表
- 测试代码
package com.kuang.leetcode10;
import java.util.HashSet;
import java.util.Set;
/**
* @ClassName ListCycle
* @Description 环形链表
* @Author 狂奔の蜗牛rz
* @Date 2021/8/31
*/
public class ListCycle {
public static void main(String[] args) {
//初始化链表结点的当前值和next指针指向值
ListNode node5 = new ListNode(5, null);
ListNode node4 = new ListNode(4, node5);
ListNode node3 = new ListNode(3, node4);
ListNode node2 = new ListNode(2, node3);
ListNode node1 = new ListNode(1, node2);
//判断hasCycle方法的返回值为true还是false
if(hasCycle2(node1)) {
//若返回值为true, 则打印如下信息
System.out.println("当前链表为环形链表!");
} else {
//若返回值为false, 则打印如下信息
System.out.println("当前链表不为环形链表!");
}
}
/**
* 使用双指针法处理环形链表
* 判断链表中是否存在环
* @param head 链表中的结点
* @return true或false 若有环则返回true, 无环则返回false
*/
public static boolean hasCycle2(ListNode head) {
//判断head(当前节点)值是否为空
if (head == null || head.next == null) {
//若head值为空, 说明该链表不存在环, 则返回值为false
return false;
}
//设置慢指针slow, 其初值为head头结点
ListNode slow = head;
//设置快指针quick, 其初值为head(头结点)的next节点值
ListNode quick = head.next;
//使用while循环, 执行条件为slow慢指针值不等于quick快指针
while(slow != quick) {
//判断quick快指针值是否为空或者quick指针的next值是否为空
if(quick == null || quick.next == null) {
//若值为空, 说明该链表不存在环, 则返回值为false
return false;
}
//将slow指针的next值赋给下一轮循环的slow指针
slow = slow.next;
//将quick指针的next值指向的next值赋给下一轮循环的quick指针
quick = quick.next.next;
}
//若慢指针值等于快指针值, 说明该链表存在环,返回值为true
return true;
}
//静态内部类ListNode
static class ListNode{
//当前节点值
int val;
//指向下一节点的next指针
ListNode next;
/**
* ListNode的有参构造方法
* @param val 当前节点值
* @param next 指向下一节点的next指针
*/
public ListNode(int val, ListNode next) {
//初始化val和next值
this.val = val;
this.next = next;
}
}
}
- 测试结果
结果:测试结果与预期相同!
2-2 环形链表测试
- 测试代码
package com.kuang.leetcode10;
import java.util.HashSet;
import java.util.Set;
/**
* @ClassName ListCycle
* @Description 环形链表
* @Author 狂奔の蜗牛rz
* @Date 2021/8/31
*/
public class ListCycle {
public static void main(String[] args) {
//初始化链表结点的当前值和next指针指向值
ListNode node5 = new ListNode(5, null);
ListNode node4 = new ListNode(4, node5);
ListNode node3 = new ListNode(3, node4);
ListNode node2 = new ListNode(2, node3);
ListNode node1 = new ListNode(1, node2);
//让节点5的next指针指向节点1, 形成了一个环形链表
node5.next = node1;
//判断hasCycle方法的返回值为true还是false
if(hasCycle2(node1)) {
//若返回值为true, 则打印如下信息
System.out.println("当前链表为环形链表!");
} else {
//若返回值为false, 则打印如下信息
System.out.println("当前链表不为环形链表!");
}
}
/**
* 使用双指针法处理环形链表
* 判断链表中是否存在环
* @param head 链表中的结点
* @return true或false 若有环则返回true, 无环则返回false
*/
public static boolean hasCycle2(ListNode head) {
//判断head(当前节点)值是否为空
if (head == null || head.next == null) {
//若head值为空, 说明该链表不存在环, 则返回值为false
return false;
}
//设置慢指针slow, 其初值为head头结点
ListNode slow = head;
//设置快指针quick, 其初值为head(头结点)的next节点值
ListNode quick = head.next;
//使用while循环, 执行条件为slow慢指针值不等于quick快指针
while(slow != quick) {
//判断quick快指针值是否为空或者quick指针的next值是否为空
if(quick == null || quick.next == null) {
//若值为空, 说明该链表不存在环, 则返回值为false
return false;
}
//将slow指针的next值赋给下一轮循环的slow指针
slow = slow.next;
//将quick指针的next值指向的next值赋给下一轮循环的quick指针
quick = quick.next.next;
}
//若慢指针值等于快指针值, 说明该链表存在环,返回值为true
return true;
}
//静态内部类ListNode
static class ListNode{
//当前节点值
int val;
//指向下一节点的next指针
ListNode next;
/**
* ListNode的有参构造方法
* @param val 当前节点值
* @param next 指向下一节点的next指针
*/
public ListNode(int val, ListNode next) {
//初始化val和next值
this.val = val;
this.next = next;
}
}
}
- 测试结果
结果:测试结果与预期相同!
3.时间和空间复杂度分析
时间复杂度分析:
使用双指针法,最多将链表遍历一遍,所以其时间复杂度为O(n)
空间复杂度分析:
并没有创建新的数组空间来存储节点,所以其空间复杂度为O(1)
好了,今天LeetCode每日一题—环形链表到这里就结束了,欢迎大家学习和讨论,点赞和收藏!
参考视频链接:https://www.bilibili.com/video/BV1Ey4y1x7J3 (国内算法宝典-LeetCode算法50讲)