BAT 經典算法筆試題 —— 逆轉單向鏈表

不善言談的優秀程序員在面試中每每是要吃鉅虧的,你沒有辦法經過說話來輕易證實本身的實力。不管是大廠仍是小廠,大部分面試官都不具有優秀的面試能力,它們也只能經過三言兩語觀察一下面試者的表面工夫。有不少這樣吃了虧的程序員,不喜歡準備面試,不喜歡吹噓虛假的不存在的經驗和能力,甚至連網上的筆試題都懶得作,由於在實際工做中這些鳥題根本一點都用不上。java

可是這並非什麼值得驕傲的真誠,面試不作準備是對目標企業的不尊重,也是我的性格上自大的一種表現。雖然全部的面試官都但願面試者是真實的無虛假的不作表面文章的,可是這樣的人真的站在你面前時,幾乎全部的面試官每每又都看不上。程序員

那如何在儘可能少作表面功夫的基礎上讓面試官可以看上你?其中的方法之一就是展示平時我的優秀的做品和實現代碼,可是這須要時間須要實打實的功夫,可以有機會做出優秀我的做品的人並很少見,大多數人都在忙碌的工做中耗盡了全部的時間。那還有一個方法就是在筆試階段大顯身手,讓本身優秀的代碼能力躍然紙上,讓面試官瞬間對你產生不同的感受。面試

不要被筆試階段那些算法題給吼到了,在你眼裏彷佛它們是爲程序天才們準備的。其實大多數公司的筆試題也是來源於網上,那些被無數人作爛的題目。你被題目搞暈了而別人沒有,那是由於別人已經作過這道題了,而不是智商所致。算法

就拿今天我要講的算法題 —— 逆轉單向鏈表,它是一個很是簡單的題目,可是若是你是第一次見到這道題,將它完完整整沒有 bug 的寫出來是着實須要費一番功夫的。指望在那短短的筆試題環節就輕鬆搞定這道題,那真是很是有算法天賦的人才能作到的事。難道大廠裏面的程序員個個都是天才,鬼才相信。天才老是極少數的,多數都是像老錢這樣的庸才。app

好,言歸正傳,下面咱們開始講解今天的算法題 —— 逆轉單向鏈表。首先這是一個單向的鏈表,不一樣於 Java 裏面的 LinkedList,它是雙向的鏈表。鏈表中每一個節點之間經過 next 指針串接起來,會有一個鏈表頭和鏈表尾指針 hold 住整個鏈表。逆轉的任務就是將 head -> a -> b -> c -> d <- tail 變成 head -> d -> c -> b -> a <- tail。oop

圖片

鏈表結構表示

圖片

class Node<T> {
	T value;
	Node<T> next;

	Node(T value) {
		this.value = value;
	}
}

class ReverseLinkedList<T> {
  Node<T> head, tail;
}
複製代碼

鏈表構造器

咱們須要將全部的元素一個一個的使用 next 指針串接起來,鏈表的頭尾節點要用 head 和 tail 變量把持住。加入新元素時,須要調整尾部節點的 next 指針,指向新加入的元素節點。 ui

圖片

class ReverseLinkedList<T> {
  private Node<T> head, tail;
  
  public ReverseLinkedList(T... values) {
	for (T value : values) {
		if (tail == null) {
            // 第一個節點
			head = tail = new Node<>(value);
		} else {
            // 後面的節點往鏈表尾部追加
			Node<T> oldTail = tail;
			oldTail.next = tail = new Node<>(value);
		}
	}
  }
}

ReverseLinkedList<Integer> l = new ReverseLinkedList<>(1,2,3,4,5,6);
複製代碼

鏈表內容呈現

咱們須要提供一個鏈表的輸出方法,以便快速對比逆轉後鏈表的內容是否正確 this

圖片

class ReverseLinkedList<T> {
  private Node<T> head, tail;

  public String toString() {
	StringBuilder sb = new StringBuilder();
	sb.append('[');
	Node<T> cur = head;
	while (cur != null) {
		sb.append(cur.value);
		cur = cur.next;
		if (cur != null) {
			sb.append(',');
		}
	}
	sb.append(']');
	return sb.toString();
  }
}
複製代碼

迭代逆轉算法

循環調整 next 指針是最容易想到的方法,可是要將指針精確地逆轉正確其實並不容易。下面代碼中的循環部分很短,可是卻很精緻,使用了三個臨時局部變量 cur、next 和 nextnext,稍有不慎就會出錯。 spa

圖片
當我寫完下面這段代碼時,雖然能夠正常運行出指望的結果,可是總小心哪裏會有遺漏,是否是什麼地方少了個 if else,這就是編寫算法代碼時常見的心理狀態。

class ReverseLinkedList<T> {
  private Node<T> head, tail public ReverseLinkedList<T> reverseByLoop() {
    // 空鏈表或者單元素都無需處理
	if (head == tail) {
		return this;
	}
	Node<T> cur = head;
	Node<T> next = cur.next;
	while (next != null) {
		Node<T> nextnext = next.next;
		next.next = cur;
		cur = next;
		next = nextnext;
	}
	tail = head;
	tail.next = null;
	head = cur;
	return this;
  }
}
複製代碼

遞歸逆轉算法

使用遞歸的思想來解決這個問題也是一個很好的主意,只不過當鏈表特別長時,調用棧會很深,鏈表長到必定程度就會拋出臭名昭著的異常 StackOverflowException。 3d

圖片

class ReverseLinkedList<T> {
  private Node<T> head, tail public ReverseLinkedList<T> reverseByRecursive() {
	Node<T> oldTail = tail;
	tail = reverseFrom(head);
	tail.next = null;
	head = oldTail;
	return this;
  }

  private Node<T> reverseFrom(Node<T> from) {
  	if (from == tail) {
	  return from;
	}
	Node<T> end = reverseFrom(from.next);
	end.next = from;
	return from;
  }
}
複製代碼

完整代碼

package leetcode;

public class ReverseLinkedList<T> {

	static class Node<T> {
		T value;
		Node<T> next;

		Node(T value) {
			this.value = value;
		}
	}

	Node<T> head;
	Node<T> tail;

	@SafeVarargs
	public ReverseLinkedList(T... values) {
		for (T value : values) {
			if (tail == null) {
				head = tail = forNode(value);
			} else {
				Node<T> oldTail = tail;
				oldTail.next = tail = forNode(value);
			}
		}
	}

	public ReverseLinkedList<T> reverseByLoop() {
		if (head == tail) {
			return this;
		}
		Node<T> cur = head;
		Node<T> next = cur.next;
		while (next != null) {
			Node<T> nextnext = next.next;
			next.next = cur;
			cur = next;
			next = nextnext;
		}
		tail = head;
		tail.next = null;
		head = cur;
		return this;
	}

	public ReverseLinkedList<T> reverseByRecursive() {
		Node<T> oldTail = tail;
		tail = reverseFrom(head);
		tail.next = null;
		head = oldTail;
		return this;
	}

	private Node<T> reverseFrom(Node<T> from) {
		if (from == tail) {
			return from;
		}
		Node<T> end = reverseFrom(from.next);
		end.next = from;
		return from;
	}

	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append('[');
		Node<T> cur = head;
		while (cur != null) {
			sb.append(cur.value);
			cur = cur.next;
			if (cur != null) {
				sb.append(',');
			}
		}
		sb.append(']');
		return sb.toString();
	}

	private static <T> Node<T> forNode(T value) {
		return new Node<>(value);
	}

	public static void main(String[] args) {
		ReverseLinkedList<Integer> rl = new ReverseLinkedList<>(1, 2, 3, 4, 5, 6);
		System.out.println(rl.reverseByRecursive().reverseByRecursive());
	}
}
複製代碼

相關文章
相關標籤/搜索