环形链表
给定一个链表,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
如果链表中存在环,则返回 true 。 否则,返回 false 。
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
输入:head = [1], pos = -1
输出:false
解释:链表中没有环。
哈希表
检查一个结点此前是否被访问过判断链表是否为环形链表
便利所有结点并在哈系表中存储每个结点的引用(或内存地址)。如果当前结点为空结点null(即已检测到链表尾部的下一个结点),已经遍历完整个链表,并且该链表不是环形链表。如果当前结点的引用已经存在于哈希表中,返回true
HashSet是一个不允许有重复元素的集合,允许有 null 值,无序的,多个线程尝试同时修改 HashSet,则最终结果是不确定的。
public static void main(String[] args) {
HashSet<String> sites=new HashSet<String>();
//添加元素,重复的元素不会被添加
sites.add("google");
sites.add("baidu");
sites.add("ali");
sites.add("ali");
System.out.println(sites);
//判断元素是否重复
System.out.println(sites.contains("baidu"));
//删除元素
System.out.println(sites.remove("ali"));
//删除所有元素
// sites.clear();
// System.out.println(sites);
System.out.println(sites.size());
//迭代HashSet中的元素
for(String i:sites) {
System.out.println(i);
}
}
HashMap 是一个散列表,它存储的内容是键值对(key-value)映射
实现了 Map 接口,根据键的 HashCode 值存储数据,具有很快的访问速度,最多允许一条记录的键为 null,不支持线程同步。
无序的,继承于AbstractMap,实现了 Map、Cloneable、java.io.Serializable 接口。
key 与 value 类型可以相同也可以不同
HashMap 类位于 java.util 包中,使用前需要引入它
import java.util.HashMap; // 引入 HashMap 类
//创建HashMap对象sites
HashMap<Integer,String> Sites=new HashMap<Integer,String>();
//添加键值对
Sites.put(1, "google");
Sites.put(2, "taobao");
Sites.put(3, "baidu");
System.out.println(Sites);
//访问元素
System.out.println(Sites.get(2));
//删除元素
System.out.println(Sites.remove(3));
//删除所有元素
//Sites.clear();
//计算HashMap中的元素数量
System.out.println(Sites.size());
//迭代HashMap
for(Integer i:Sites.keySet()) {
System.out.println("key:"+i+" value:"+Sites.get(i));
}
Set Set 具有与 Collection 完全一样的接口,只是行为上不同,Set 不保存重复的元素。Set 接口存储一组唯一,无序的对象。
Set是一个接口,而HashSet是一个类。 HashSet继承并实现了Set
Set set = new HashSet();这句创建了一个HashSet的对象后把上溯到了Set。此时它是一个Set引用了,有些HashSet有但是Set没有的属性和方法,它就不能再用了。而HashSet hs=new HashSet();创建一对象则保留了HashSet的所有属性。
Set有多个实现类,如LinkedSet等,现在使用的是HashSet,也许哪一天需要换成其它的实现类呢?你这个方法的返回类型就不需要改了,并且调用这个方法的所有类都不需要修改,这样就降低了修改程序的成本和风险,提高了程序的可复用性。
便于程序代码的重构. 这就是面向接口编程的好处。如果是写共通类共通方法的话,建议少用特性,多用面向接口。
public boolean hasCycle(ListNode head) {
Set<ListNode> node=new HashSet<>();
while(head!=null) {
if(node.contains(head)) {
return true;
}else {
node.add(head);
head=head.next;
}
}
return false;
}
时间复杂度:O(n),访问每个元素最多一次,添加一个结点
空间复杂度:O(n),取决于添加到哈希表中的元素数目
双指针
快慢指针遍历链表,空间复杂度可以降为O(1)。
快指针移动两步,满指针移动一步
链表不存在环,快指针会先到达尾部,返回false。
public boolean hasCycle(ListNode head) {
ListNode q,s;
q=head;
s=head;
if(q!=null&&q.next!=null) {
if(q.next==q)
return true;
}
while(q!=null&&q.next!=null) {
//q=q.next.next;
q=q.next;
q=q.next;
s=s.next;
if(q==s)
return true;
}
return false;
}