1.链表中的环的问题
1.1集合解法
将链表元素依次存入Set中,因为Set集合是不允许存入重复元素,若存入失败,则是环形链表,并返回环的入口
public static 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;
}
1.2双指针解法
使用集合解法很简单,但是需要开辟O(n)的空间,而双指针只需O(1)的空间,因此在该题中更加高效。
一个快指针(一次走两步),一个慢指针(一次走一步),若有环,两个指针一定会相遇。
确定有没有环很简单,找到环的入口的思路却不简单。
思路:在快慢指针相遇的那个节点(假定为下图的Z节点)。让其中一个指针回到头节点(X),另一个仍在相遇节点(Z),然后让快慢指针以同一速度开始遍历,当它们再次相遇的节点(Y)就是环的入口
快指针在环内跑一圈后相遇:
第一次相遇在Z节点,此时快指针的路径为:a+b+c+b,慢指针为:a+b
因为快指针的速度是慢指针的一倍,所以:a+b+c+b = 2(a+b)
即a = c
因此让slow从Z继续向前走,fast回到起点,两个指针以同一速度遍历(一次一步),slow走路径c,fast走路径a,最后会相遇在Y节点,即环的入口
快指针在环内跑多圈后相遇
快指针路径:a+n(b+c)+b
因为快指针的速度是慢指针的一倍,所以:a+n(b+c)+b = 2(a+b)
即:a = (n-1)b+nc ,又因b+c为环的长度,假定为len
即:a = c+(n-1)len ,多次相遇前,快指针已经在环里跑了(n-1)len圈
n=1 时,就是上述跑一圈后相遇的状况了,n=2、3、4...时,意思是slow指针重新定位到头节点(X),fast指针在相遇节点(Z),它们以同一速度遍历时,都会在环入口处相遇,只不过fast指针要先在环内跑(n-1)len圈
public ListNode detectCycle(ListNode head) {
if(head==null) return null;
ListNode fast = head;
ListNode slow = head;
while(fast!=null){
slow = slow.next;
if(fast.next!=null){
fast = fast.next.next;
}else return null;
if(fast==slow){
slow = head; //重新定位到头节点
while(fast!=slow){
fast = fast.next;
slow = slow.next;
}
return slow;
}
}
return null;
}
2.双向链表
/**
* 创建双向链表结点
*/
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 + "} ");
}
}
2.1遍历链表
//从头部开始演绎
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.prev;
}
System.out.println();
}
2.2插入元素
2.2.1头尾插入
//从头部删除结点
public DoubleNode deleteFirst() {
DoubleNode temp = first;
if (first.next == null) { //若链表只有一个结点,删除后链表为空,将last指向null
last = null;
} else {
first.next.prev = null; //若链表有两个(包括两个)以上的结点 ,因为是头部插入,则first.next将变成第一个结点,其previous将变成null
}
first = first.next; //将first.next赋给first
return temp; //返回删除的结点
}
//从尾部删除结点
public DoubleNode deleteLast() {
DoubleNode temp = last;
if (first.next == null) { //如果链表只有一个结点,则删除以后为空表,last指向null
first = null;
} else {
last.prev.next = null; //将上一个结点的next域指向null
}
last = last.prev; //上一个结点称为最后一个结点,last指向它
return temp; //返回删除的结点
}
2.2.2 中间插入
//某个结点的后部插入
public void insertAfter(int key, int data) {
DoubleNode newDoubleNode = new DoubleNode(data);
DoubleNode current = first;
while ((current != null) && (current.data != key)) {
current = current.next;
}
//若当前结点current为空
if (current == null) { //current为null有两种情况 一种是链表为空,一种是找不到key值
if (isEmpty()) { //1、链表为空
first = newDoubleNode; //则插入第一个结点(其实可以调用其它的Insert方法)
last = newDoubleNode; //first和last均指向该结点(第一个结点)
} else {
last.next = newDoubleNode; //2、找不到key值
newDoubleNode.prev = last; //则在链表尾部插入一个新的结点
last = newDoubleNode;
}
} else {
if (current == last) { //第三种情况,找到了key值,分两种情况
newDoubleNode.next = null; //1、key值与最后结点的data相等
last = newDoubleNode; //由于newNode将是最后一个结点,则将last指向newNode
} else {
newDoubleNode.next = current.next; //2、两结点中间插入 四
current.next.prev = newDoubleNode; //将current当前结点的下一个结点赋给newNode.next
} //将current下一个结点即current.next的previous域指向current
current.next = newDoubleNode; //将当前结点的next域指向newNode
newDoubleNode.prev = current; //将新结点的previous域指向current(current在newNode前面一个位置)
}
}
2.2删除节点
2.2.1删除头尾
//从头部删除结点
public DoubleNode deleteFirst() {
DoubleNode temp = first;
if (first.next == null) { //若链表只有一个结点,删除后链表为空,将last指向null
last = null;
} else {
first.next.prev = null; //若链表有两个(包括两个)以上的结点 ,因为是头部插入,则first.next将变成第一个结点,其previous将变成null
}
first = first.next; //将first.next赋给first
return temp; //返回删除的结点
}
//从尾部删除结点
public DoubleNode deleteLast() {
DoubleNode temp = last;
if (first.next == null) { //如果链表只有一个结点,则删除以后为空表,last指向null
first = null;
} else {
last.prev.next = null; //将上一个结点的next域指向null
}
last = last.prev; //上一个结点称为最后一个结点,last指向它
return temp; //返回删除的结点
}
2.2.2删除中间节点
//按值删除
public DoubleNode deleteKey(int key) {
DoubleNode current = first;
while (current != null && current.data != key) { //遍历链表寻找该值所在的结点
current = current.next;
}
if (current == null) { //若当前结点指向null则返回null,
return null; //两种情况当前结点指向null,一是该链表为空链表,而是找不到该值
} else {
if (current == first) { //如果current是第一个结点
first = current.next; //则将first指向它,将该结点的previous指向null,其余不变
current.next.prev = null;
} else if (current == last) { //如果current是最后一个结点
last = current.prev; //将last指向当前结点的上一个结点(我们将当前结点除名了以后它便不再是最后一个了)
current.prev.next = null; //相应的要删除结点的上一个结点的next域应指向null
} else {
current.prev.next = current.next; //当前结点的上一个结点的next域应指向当前的下一个结点
current.next.prev = current.prev; //当前结点的下一个结点的previous域应指向当前结点的上一个结点
}
}
return current; //返回
}