1. 程式人生 > >5個常見的連結串列操作

5個常見的連結串列操作

1 單鏈表翻轉

1.1 迭代版本

時間複雜度 O(n),空間複雜度 O(1)

ListNode ReverseList(ListNode head)
{
	ListNode temp = null, nextNode = null;

	while(head != null) {
		nextNode = head;
		head.setNext(temp);
		temp = head;
		head = nextNode;
	}
	return temp;
}

1.2

public static Node reverse(Node list){
	Node headNode = null;
Node preNode = null; Node currNode = list; while( currNode != null) { Node nextNode = currNode.next; if(nextNode == null) { headNode = currNode; } currNode.next = preNode; preNode = currNode; currNode = nextNode; } return headNode; }

2 連結串列中環的檢測

Floyd 環判定法,使用在連結串列中具有不同移動速度的指標,一旦它們進入環就會相遇,即表示存在環。

boolean DoesListContainsLoop(ListNode head){
	//時間複雜度O(n),空間複雜度O(1)
	if(head==null) return fasle;
	ListNode slowPtr=fastPtr=head;
	while(fastPtr.getNext()!=null && fastPtr.getNext().getNext()!=null) {
		slowPtr=slowPtr.getNext();
		fastPtr=fastPtr.getNext().getNext();
		if(slowPtr==fastPtr)
			return
true; } return false; }
public static boolean checkCircle(Node list){
	if(list == null) return false;
	Node fast = list.next;
	Node slow = list;

	while(fast != null && fast.next != null) {
		fast = fast.next.next;
		slow = slow.next;

		if(slow == fast) return true;
	}

	return fasle;
}

2.1 判斷給定的連結串列是否存在環,如果存在,找到環的起始點

思路分析:在找到環後,初始化 slowPtr 使其指向表頭節點。然後slowPtr 和 fastPtr 從各自的位置開始移動,每次只移動一個節點,它們相遇的位置就是環的起始位置。(可以用這種方法刪除環)

ListNode FindBerginofLoop(ListNode head){
// 時間複雜度 O(n),空間複雜度 O(1)

	if(head==null)
		return null;
	ListNode slowPtr=fastPtr=head;
	boolean loopExists=false;
	while(fastPtr.getNext()!=null && fastPtr.getNext().getNext()!=null) {
		slowPtr = slowPtr.getNext();
		fastPtr = fastPtr.getNext().getNext();
		if(slowPtr==fastPtr) {
			loopExists = true;
			break;
		}
	}

	if(loopExists) {
		slowPtr=head;
		while(slowPtr!=fastPtr) {
			slowPtr = slowPtr.getNext();
			fastPtr = fastPtr.getNext();
		}
		return slowPtr; //返回環的開始節點
	}

	return null; //環不存在

}

2.2 在Floyd 環判定演算法中,如果兩個指標每次分別移動2個節點和3個節點,而不是移動1個節點和2個節點,演算法任然有效嗎?

有效,但是複雜度可能增加。

2.3 判定給定的連結串列中是否存在環,若存在,返回環的長度

在找到連結串列中存在環後,保持slowPtr的指標不變,fastPtr 指標繼續移動。每次移動 fastPtr 指標時,計數器變數加 1 ,直到 再一次回到 slowPtr 指標所在的位置。

// 時間複雜度 O(n),空間複雜度 O(1)
int findLoopLength(ListNode head){
	ListNode slowPtr = fastPtr = heaed;
	boolean loopExists = fasle;
	int count = 0;
	if(head == null) return 0;

	while(fastPtr.getNext() != null && fastPtr.getNext().getNext() != null) {
		slowPtr = slowPtr.getNext();
		fastPtr = fastPtr.getNext().getNext();
		if(slowPtr == fastPtr) {
			loopExists = true;
			break;
		}
	}

	if(loopExists) {
		fastPtr = fastPtr.getNext();
		count++;
		while(slowPtr != fastPtr) {
			fastPtr = fastPtr.getNext();
			count++;
		}
		count++;
		return count;
	}

	return 0;
}

3 兩個有序連結串列的合併

3.1 方法1

// 第一個 while 迴圈,將l1 和 l2 進行比較,誰小就合併到 listNode,直到l1 或者 l2 為空
//  第二、三個 while 迴圈 將了l1 或者l2 剩下的節點合併到 listNode

ListNode mergeSortedList(ListNode l1,ListNode l2){
	ListNode listNode = new ListNode(0);
	ListNode head = listNode;

	while(l1 != null && l2 != null) {
		if(l1.data <= l2.data) {
			listNode.next = l1;
			l1 = l1.next;
		}else{
			listNode.next = l2;
			l2 = l2.next;
		}
		listNode = listNode.next;
	}

	while(l1 != null) {
		listNode.next = l1;
		l1 = l1.next;
		listNode = listNode.next;
	}

	while(l2 != null) {
		listNode.next = l2;
		l2 = l2.next;
		listNode = listNode.next;
	}

	return head.next;
}

3.2 方法2 遞迴法

//分治思想,每次拿一個小的出來,每次的動作相同
ListNode MergeLists(ListNode a,ListNode b){
	ListNode result = null;
	if(a==null) return b;
	if(b==null) return a;

	if(a.getData() <= b.getData()) {
		result = a;
		result.setNext(MergeLists(a.getNext(),b));
	}else{
		result = b;
		result.setNext(MergeLists(b.getNext(),a));
	}

	return result;
}

3.3

public static Node mergeSortedLists(Node la,Node lb){
	if(la == null) return lb;
	if(lb == null) return la;

	Node p = la;
	Node q = lb;
	Node head;

	if(p.data < q.data) {
		head = p;
		p = p.next;
	}else{
		head = q;
		q = q.next;
	}
	Node r = head;

	while(p != null && q!= null) {
		if(p.data < q.data) {
			r.next = p;
			p = p.next;
		}else{
			r.next = q;
			q = q.next;
		}
		r = r.next;
	}

	if(p != null) {
		r.next = p;
	}else{
		r.next = q;
	}

	return head;

}

4 刪除連結串列倒數第 n 個節點

public static Node deleteLastKth(Node list,int k){
	Node fast = list;
	int i = 1;
	while( fast != null && i < k) {
		fast = fast.next;
		++i;
	}

	if(fast == null) return list;

	Node slow = list;
	Node pre = null;
	while(fast.next != null) {
		fast = fast.next;
		pre = slow;
		slow = slow.next;
	}

	if(pre == null) {
		list = list.next;
	}else{
		pre.next = pre.next.next;
	}

	return list;

}

5 求連結串列的中間節點

5.1 蠻力法

在連結串列中對每個節點統計其後的節點的個數,然後判定其是否為中間節點。 時間複雜度 O(N^2),空間複雜度 O(1)

5.2 一次掃描搞定

時間複雜度 O(n),空間複雜度 O(1) 讓第一個指標的移動速度是另一個的2倍,當第一個到達表尾的時候,另一個指標則指向中間節點。

ListNode findMiddle(ListNode head){
	ListNode slowPtr = fastPtr = head;
	//不斷迴圈,直到達到表尾(next的後繼指標為 null,表示達到最後一個節點)
	int i = 0;
	while(fastPtr.getNext() != null) {
		if(i == 0) {
			fastPtr = fastPtr.getNext();
			i = 1;
		}
		if(i == 1) {
			fastPtr = fastPtr.getNext();
			slowPtr = slowPtr.getNext();
			i = 0;
		}

	}

	return slowPtr;
}

public static Node findMidNode(Node list){
	if(list == null) return null;
	Node first = list;
	Node slow = list;

	while(fast.next != null && fast.next.next != null) {
		fast = fast.next.next;
		slow = slow.next;
	}

	return slow;

}