Java面試題4-數據結構與算法基礎


說一下幾種常見的排序算法和分別的復雜度

 

倒排一個LinkedList

Collecionts.reverse(List<?> list)

 

什么是跳表

 

聽到跳表(skiplist)這個名字,既然是list,那么應該跟鏈表有關。 跳表是有序鏈表,但是我們知道,即使對於排過序的鏈表,我們對於查找還是需要進行通過鏈表的指針進行遍歷的,時間復雜度很高依然是O(n),這個顯然是不能接受的。是否可以像數組那樣,通過二分法進行查找呢,但是由於在內存中的存儲的不確定性,不能這做。  但是我們可以結合二分法的思想,沒錯,跳表就是鏈表與二分法的結合。 1.鏈表從頭節點到尾節點都是有序的 2.可以進行跳躍查找(形如二分法),降低時間復雜度  一個有序的鏈表,我們選取它的一半的節點用來建索引,這樣如果插入一個節點,我們比較的次數就減少了一半。 這種做法,雖然增加了50%的空間,但是性能提高了一倍。 既然,我們已經提取了一層節點索引,那么,可以在第一層索引上再提取索引。

 

如何確認一個鏈表有環?進一步,確認環的位置


這是一道很常見的面試問題,,只用兩個變量通過O(n)的時間復雜度就可以解決。

Floyd cycle detection算法,也叫做tortoise and hare算法,龜兔算法吧。

http://en.wikipedia.org/wiki/Cycle_detection#Tortoise_and_hare

以下內容為對思路重新梳理了一下,參考了stackoverflow論壇的討論

http://stackoverflow.com/questions/2936213/explain-how-finding-cycle-start-node-in-cycle-linked-list-work

http://stackoverflow.com/questions/494830/how-to-determine-if-a-linked-list-has-a-cycle-using-only-two-memory-locations。

和thestoryofsnow的博客

http://blog.csdn.net/thestoryofsnow/article/details/6822576

代碼來自thefutureisour

http://blog.csdn.net/thefutureisour/article/details/8174313

原文鏈接:https://blog.csdn.net/duxinyuhi/article/details/53379239

 

龜兔解法的基本思想可以用我們跑步的例子來解釋,如果兩個人同時出發,如果賽道有環,那么快的一方總能追上慢的一方。進一步想,追上時快的一方肯定比慢的一方多跑了幾圈,即多跑的路的長度是圈的長度的倍數。

 

基於上面的想法,Floyd用兩個指針,一個慢指針(龜)每次前進一步,快指針(兔)指針每次前進兩步(兩步或多步效果是等價的,只要一個比另一個快就行,從后面的討論我們可以看出這一點。ps:考慮一個鏈表A有1000000個結點的環,鏈表B是有1000000個的非環的結點,然后再加一個只有3個結點的小環,如果兔子跑的更快點,即每次前進多於2步,那么能更快的檢測鏈表A,但鏈表B就很慢,因為要一直繞圈等烏龜,所以選擇2步是一個tradeoff)。如果兩者在鏈表頭以外的某一點相遇(即相等)了,那么說明鏈表有環,否則,如果(快指針)到達了鏈表的結尾,那么說明沒環。

證明如下:

 

這樣做的道理我用下圖解釋

假設起點到環的起點距離為m,已經確定有環,環的周長為n,(第一次)相遇點距離環的起點的距離是k。那么當兩者相遇時,慢指針移動的總距離為i,因為快指針移動速度為慢指針的兩倍,那么快指針的移動距離為2i

 

1) i = m + p * n + k

2) 2i = m + q * n + ki

 

其中,p和q分別為慢指針和快指針在第一次相遇時轉過的圈數。

 

2 ( m + p * n + k ) = m + q * n + k

=> 2m + 2pn + 2k = m + nq + k

=> m + k = ( q - 2p ) n

 

只要有一組p,q,k滿足這個式子,假設就成立了。

我們假設

 

p = 0

q = m

k = m n - m

 

那么我們證明如下

 

m + k = ( q - 2p ) n

=> m + mn - m = ( m - 2*0) n

=> mn = mn.

 

這時,i為

 

i = m + p n + k

=> m + 0 * n + mn - m = mn.

假設成立。

 

環的檢測

 

環的檢測是Floyd解法的第二部分。

一旦烏龜和兔子相遇,將慢指針移到鏈表起點,快指針還在他們相遇的地方,即離環的起點距離k的地方。然后慢指針和快指針同時移動,每次移動一步,那么兩者再次相遇的地方就是環的起點。

 

證明如下:

讓烏龜和兔子都同時移動m+k步,烏龜到了他們最初遇見的地方(遠離環起點k的位置)

之前我們得到 m + k = (q - 2p) n

即兔子走了q-2p圈,也到了和烏龜同樣的位置

現在我們不讓烏龜走 m+k 步,讓烏龜只走 m 步,兔子也后退k步,即到了環的起點,這是他們第一次相遇的地方。

當他們相遇時,烏龜走的步數就是環的地點在的位置。

求環的長度的問題,第一次相遇后,再次相遇時,走的距離就是環的長度。

 

如何遍歷一棵二叉樹

 

image.png


 

 


二叉樹的深度優先遍歷和廣度優先遍歷

 

廣度優先遍歷

英文縮寫為BFS即Breadth FirstSearch。其過程檢驗來說是對每一層節點依次訪問,訪問完一層進入下一層,而且每個節點只能訪問一次。對於上面的例子來說,廣度優先遍歷的 結果是:A,B,C,D,E,F,G,H,I(假設每層節點從左到右訪問)。

先往隊列中插入左節點,再插右節點,這樣出隊就是先左節點后右節點了。

 廣度優先遍歷樹,需要用到隊列(Queue)來存儲節點對象,隊列的特點就是先進先出。例如,上面這顆樹的訪問如下:

首先將A節點插入隊列中,隊列中有元素(A);

將A節點彈出,同時將A節點的左、右節點依次插入隊列,B在隊首,C在隊尾,(B,C),此時得到A節點;

繼續彈出隊首元素,即彈出B,並將B的左、右節點插入隊列,C在隊首,E在隊尾(C,D,E),此時得到B節點;

繼續彈出,即彈出C,並將C節點的左、中、右節點依次插入隊列,(D,E,F,G,H),此時得到C節點;

將D彈出,此時D沒有子節點,隊列中元素為(E,F,G,H),得到D節點;

 

public class Solution{  private List<Integer> bfs(TreeNode root){  List<Integer> result = new ArrayList<>();  if(root == null) return result;  Queue<TreeNode> queue = new LinkedList<>();  queue.add(root);  while(!queue.isEmpty()){  TreeNode node = queue.poll();  if(node.left != null) queue.add(node.left);  if(node.right != null) queue.add(node.right);  result.add(node.val);  }  return result;  } }

深度優先遍歷

英文縮寫為DFS即Depth First Search.其過程簡要來說是對每一個可能的分支路徑深入到不能再深入為止,而且每個節點只能訪問一次。對於上面的例子來說深度優先遍歷的結果就是:A,B,D,E,I,C,F,G,H.(假設先走子節點的的左側)。

 

深度優先遍歷各個節點,需要使用到棧(Stack)這種數據結構。stack的特點是是先進后出。整個遍歷過程如下:

先往棧中壓入右節點,再壓左節點,這樣出棧就是先左節點后右節點了。

首先將A節點壓入棧中,stack(A);

將A節點彈出,同時將A的子節點C,B壓入棧中,此時B在棧的頂部,stack(B,C);

將B節點彈出,同時將B的子節點E,D壓入棧中,此時D在棧的頂部,stack(D,E,C);

將D節點彈出,沒有子節點壓入,此時E在棧的頂部,stack(E,C);

將E節點彈出,同時將E的子節點I壓入,stack(I,C);

…依次往下,最終遍歷完成。

public class Solution{  private List<Integer> dfs(TreeNode node){  List<Integer> list = new ArrayList<>();  if(root == null) return list;  Stack<Integer> stack = new Stack<>();  stack.push(root);  while(!stack.isEmpty()){  TreeNode node = stack.pop();  if(node.right != null) stack.push(node.right);  if(node.left != null) stack.push(node.left);  result.add(node.val);  }  return result;  } }

 

 

HashSet的實現方式

 

  1. HashSet概述:

   HashSet實現Set接口,由哈希表(實際上是一個HashMap實例)支持。它不保證set 的迭代順序;特別是它不保證該順序恆久不變。此類允許使用null元素

 

堆和棧在內存中的區別是什么

 

  • 棧內存存儲的是局部變量而堆內存存儲的是實體;
  • 棧內存的更新速度要快於堆內存,因為局部變量的生命周期很短;
  • 棧內存存放的變量生命周期一旦結束就會被釋放,而堆內存存放的實體會被垃圾回收機制不定時的回收

 

什么是深拷貝和淺拷貝

 

淺拷貝: 是一個傳址,也就是把a的值賦給b的時候同時也把a的址賦給了b,當b(a)的值改變的時候,a(b)的值同時也會改變

深拷貝:深拷貝是指,拷貝對象的具體內容,二內存地址是自主分配的,拷貝結束之后倆個對象雖然存的值是一樣的,但是內存地址不一樣,倆個對象頁互相不影響,互不干涉


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM