1.链表中环的问题
leetcode141:给定一个链表,判断链表中是否有环?
leetcode142:假如有环,那环的位置在哪里?
判断是否有环,最容易的方法就是使用hash,遍历的时候将元素放入map中,如果有环,一定会发生碰撞;发生碰撞的位置也就是入口位置。
public ListNode detectCycle(ListNode head){
ListNode pos = head;
Set<ListNode> visited = new HashSet<ListNode>();
while(pos != null){
if(visited.contains(pos)){
return pos;
}else{
visited.add(pos);
}
pos = pos.next;
}
return null;
}
list集合中contains() 用于判断集合中 是否 包含指定的元素。list会将括号内的元素和list中存在的元素进行逐个比对,若有相等的,返回结果为true,若没有则返回结果为false。
Java中List.contains()方法比较的是地址而不是值
----------------------------------------------------------------------------------------------------------------
如果只有O(1)的空间该怎么做?
1.1为什么快慢指针一定会相遇?
确定是否有环,最有效的方法就是双指针,一个快指针(一次走两步),一个慢指针(一次走一步)。如果快指针能到达表尾就不会有环,否则如果存在环,慢指针在某个位置与快指针相遇。
public ListNode hasCycle(ListNode head){
if(head == null || head.next == null){
return false;
}
ListNode fast = head;
ListNode slow = head;
while(fast != null && fast.next != null){
fast = fast.next.next
slow = slow.next;
if(fast == slow){
return ture;
}
}
return false;
}
1.2确定入口的方法
结论:先按照快慢的方式寻找到相遇的位置(假如为下图中的Z),然后将两指针分别放在链表头(X)和相遇位置(Z),并改为相同速度前进,则,两指针在环开始的位置(Y)相遇。
为什么呢?
假设一圈就相遇了,快指针在第二次进入环的时候就相遇了:
此时过程为:
1.找环中相遇点。分别用fast、slow表示快慢指针,slow每次走一步,fast每次走两步,直到在环中某个位置相遇,假设为(Z).
2.第二次相遇:
fast指针走了a+b+c+b步,slow指针走了a+b步;
那么2(a+b)=a+b+c+b => a=c;
因此我们让slow从Z开始走,fast回到起点,两个同时走(每次都走一步),那么他们就会在环的起点相遇。
如果多圈之后才相遇
设链表环外部分为a。slow指针进入环后,又走了b的距离与fast相遇,此时,fast已经走了n圈;
fast走的总距离为:a+n(b+c)+b 设环的长度为LEN = b+c
则a+n(b+c)+b = 2(a+b) a = c+(n-1)LEN
public ListNode detectCycle(ListNode head){
if(head == null){
return null;
}
ListNode slow = head,fast = head;
while(fast != null){
slow = slow.next;
if(fast.next != null){
fast = fast.next.next;
}else{
return null;
}
if(fast == slow){
ListNode ptr = head;
while(ptr != slow){
ptr = ptr.next;
slow = slow.next;
}
return ptr;
}
}
return null;
}
2.双向链表
2.1基本概念
双向链表的结点:
class doubleNode{
public int data; //数据域
public DoubleNode next; //指向下一个结点
public DoubleNode prev; //指向前一个结点
public DoubleNode(int data){
this.data = data;
}
//打印结点的数据域
public void displayNode(){
System.out.print("{"+ data +"}");
}
}
双向链表的结构和遍历的方法:
public class DoublyLinklist{
private DoubleNode first;
private DOubleNode last;
public DoublyLinkList(){
first = null;
last = first;
}
//从头部开始打印
public void displayForward(){
System.out.print("List(first--->last):");
DoubleNode current = first;
while(current != null){
current.displayNode();
current = current.next;
}
System.out.println();
}
//从尾部开始打印
public void displayBackward(){
System.out.print("List(last--->first):");
DoubleNode current = last;
while(current != null){
current.displayNode();
current = current.next;
}
System.out.println();
}
}
2.2链表类的定义以及方法:
public class LinkList{
static class ListNode{//节点是链表的属性,所以用static来修饰
//节点成员
public int val;
public ListNode prev;
public ListNode next;
public ListNode(int val){
this.val = val;
}
}
public ListNode head;
public ListNode last;
//头插法
public void addFirst(int data){}
//尾插法
public void addLast(int data){}
//在任意节点位置插入,假设第一个节点下标为0
public void addIndex(int index,int data){}
//查找是否包含关键字key是否在单链表中
public boolean contains(int key){}
//删除第一次出现关键字为key的节点
public void remove(int key){}
//删除所有值为key的节点
public void removeAllKey(int key){}
//得到链表的长度
public int size(){}
//打印链表内容
public void display(){}
//删除链表
public void clear(){}
}
头插法addFirst():
public void addFirst(int data){
ListNode node = new ListNode(data);//创建一个节点
if(head == null){//无元素情况
head = node;
last = node;
return;
}
head.prev = node;
node.next = head;
head = node;
}
尾插法addLast():
public void addLast(int data){
ListNode node = new ListNode(data);
if(head == null){
head == node;
last == node;
return;
}
last.next = node;
node.prev = last;
last = node;
}
链表长度size()
public int size(){
ListNode cur = head;
int count = 0;
while(cur != null){
cur = cur.next;
count++;
}
return count;
}
打印链表内容display()
public void display(){
ListNode cur = head;
while(cur != null){
System.out.print(cur.val + " ");
cur = cur.next;
}
System.out.println();
}
删除链表clear()
public void clear(){
ListNode cur = head;
while(cur != null){
ListNode curNext = cur.next;
cur.prev = null;
cur.next = null;
cur = curNext;
}
head = null;
last = null;
}
在任意位置插入addIndex()
1.先看输入的下标是否合法
2.看下标是否是头结点,尾节点,是的话直接调用对应方法
3.如下图
public void addIndex(int index,int data){
if(index < 0 || index > size()){
System.out.print("index输入不合法");
}
if(index == 0){
addFirst(data);
return;
}
if(index == size()){
addLast(data);
return;
}
//用cur找到插入位置
ListNode cur = head;
while(index != 0){
cur = cur.next;
index--;
}
ListNode node = new ListNode(data);
node.prev = cur.prev;
node.next = cur;
cur.prev.next = node;
cur.prev = node;
}
链表当中是否有key contains(key)
public boolean contains(int key){
ListNode cur = head;
while(cur != null){
if(cur.val == key){
return ture;
}
cur = cur.next;
}
return false;
}
删除链表中第一次出现的key remove(int key)
1.先找到key所在的位置
2.判断是否是头结点,如果是且不只有一个元素
如果是头结点且只有一个元素
3.再判断是否是尾节点,如果是:
4.如果是中间节点:
public void remove(int key){
ListNode cur = head;
while(cur != null){
if(cur.val == key){
if(cur == head){
head = head.next;
if(head == null){//只有一个元素
last = null;
}else{//有多个元素
head.prev = null;
}
}else{
if(cur == last){//尾节点
last = last.prev;
last.next = null;
}else{//中间节点
cur.prev = cur.next;
cur.next.prev = cur.prev;
}
}
return;
}
cur = cur.next;
}
}
删除所有值为key的节点 removeAllKey(int key)
和上面删除一个一样,就是删除完后不要return
public void remove(int key){
ListNode cur = head;
while(cur != null){
if(cur.val == key){
if(cur == head){
head = head.next;
if(head == null){//只有一个元素
last = null;
}else{//有多个元素
head.prev = null;
}
}else{
if(cur == last){//尾节点
last = last.prev;
last.next = null;
}else{//中间节点
cur.prev = cur.next;
cur.next.prev = cur.prev;
}
}
//return;
}
cur = cur.next;
}
}