Java演算法面試題(002) 如何通過一次迭代找到LinkedList的中間元素

劉近光發表於2017-11-22

宣告:本文為本博主翻譯,未經允許,嚴禁轉載!

簡介

Java和非Java程式設計師在電話面試中經常被問及如何僅一次遍歷查詢到LinkedList的中間元素。這個問題類似於檢查迴文或者計算階乘,面試官有時也會要求編寫程式碼。為了回答這個問題,候選人必須熟悉LinkedList資料結構,即在單LinkedList的情況下,連結串列的每個節點都包含資料和指標,它是下一個連結串列節點的地址,單連結串列的最後一個元素指向null。由於為了找到連結串列的中間元素,你需要找到LinkedList的長度,這需要對連結串列節點進行計數直到最後一個元素為止。讓這個資料結構面試問題有趣的是,你需要在一次遍歷中找到LinkedList的中間元素,而你不知道LinkedList的長度。這是考驗候選人邏輯能力的地方:他是否熟悉空間和時間的權衡等等。
 
如果你仔細想想,你可以通過使用兩個指標來解決這個問題,就像我在上一篇關於如何在Java中查詢單一連結串列的長度的文章中提到的那樣。通過使用兩個指標,其中一個每次迭代增一,另外一個每兩次迭代增。當第一個指標指向連結串列的末尾時,第二個指標將指向連結串列的中間節點。

實際上,這兩個指標方法可以解決多個相似的問題,如何在一個迭代中從連結串列中找到倒數第3個元素,或者如何在連結串列中找到倒數第N個元素。在這個Java程式設計教程中,我們將看到一個Java程式,它在一個迭代中查詢連結列表的中間元素。


這裡是完整的Java程式來查詢Java中的連結串列的中間節點。記住LinkedList類是我們的自定義類,不要把這個類與Java中流行的Collection類java.util.LinkedList混淆。在這個Java程式中,我們的類LinkedList表示一個包含節點集合並具有頭部和尾部的連結串列資料結構。每個節點都包含資料和地址部分。 LinkedListTest類的主要方法是用來模擬問題的,在這裡我們建立了Linked List,並在其上新增了一些元素,然後遍歷它們在Java中一次遍歷連結列表的中間元素。

通過一次LinkedList迭代查詢中間元素的Java程式

/**
 * Java program to find middle element of linked list in one pass. In order to
 * find middle element of linked list we need to find length first but since we
 * can only traverse linked list one time, we will use two pointers one which we
 * will increment on each iteration while other which will be incremented every
 * second iteration. so when first pointer will point to the end of linked list,
 * second will be pointing to the middle element of linked list
 * 
 * @author
 */

public class LinkedListTest {
	public static void main(String[] args) {
		// creating LinkedList with 5 elements including head
		LinkedList list = new LinkedList();
		
		list.add(new LinkedList.Node("1"));
		list.add(new LinkedList.Node("2"));
		list.add(new LinkedList.Node("3"));
		list.add(new LinkedList.Node("4"));
		list.add(new LinkedList.Node("5"));
		
		LinkedList.Node head = list.head();

		// finding middle element of LinkedList in a single pass
		LinkedList.Node current = head;
		LinkedList.Node middle = head;
		int length = 0;

		while (current.next() != null) {
			length++;

			if (length % 2 == 0) {
				middle = middle.next();
			}

			current = current.next();
		}
		length++;


		System.out.println("length of LinkedList: " + length);
		System.out.println("middle element of LinkedList : " + middle);

	}
}

class LinkedList {
	private Node head;
	private Node tail;

	/**
	 * Constructs an empty list
	 */
	public LinkedList() {
		head = null;
		tail = null;
	}

	public Node head() {
		return head;
	}

	public void add(Node node) {
		if (head == null) {
			head = node;
			tail = node;
			
			return;
		}
		
		tail.next = node;
		tail = node;
	}

	public static class Node {
		private Node next;
		private String data;

		public Node(String data) {
			this.data = data;
		}

		public String data() {
			return data;
		}

		public void setData(String data) {
			this.data = data;
		}

		public Node next() {
			return next;
		}

		public void setNext(Node next) {
			this.next = next;
		}

		public String toString() {
			return this.data;
		}
	}
}

原文連結

How to find middle element of LinkedList in Java in one pass in Java

譯者備註

1. 譯文中實現與原實現有些差異,原文中在連結串列初始化時自動建立了一個無關節點,相關實現也不容易理解,這塊在上文中譯者進行了優化,感興趣的可以對比一下原文的實現。

2. 你應該搞清楚迭代一次是什麼意思。如果面試官說你不能迴圈兩次而只需要使用一個迴圈,那麼你可以使用兩個指標來解決這個問題。在兩個指標方法中,你有兩個指標,快指標和慢指標。在每個步驟中,快速指標移動兩個節點,而慢速指標僅移動一個節點。所以,當快速指標指向最後一個節點,即下一個節點為空時,慢速指標將指向連結串列的中間節點。

3. 這裡再舉一個類似的案例,可以使用本文的演算法解決: 例如,假設你有一個連結串列a1  - > a2  - > ...  - > an  - > b1  - > b2  - > ...  - > bn,將其重新排列為a1→b1→a2→b2→b→a→bn。你不知道連結串列的長度(但是你要知道長度是偶數)。你可以有一個指標p1(快速指標)為每次移動兩個元素,而p2(慢速指標)每次移動一個元素。當p1到達連結串列的末尾時,p2將處於中點。然後,將p1移回前面並開始組織的元素。在每次迭代中,p2選擇一個元素並將其插入到p1之後。

相關文章