面試演算法:雙指標單向連結串列的自我複製
有一種較為特殊的單向連結串列,如下:
這種連結串列一個特點是,除了next指向下一個節點外,它還多了一個指標jump,這個指標指向佇列中的某一個節點,這個節點可以是當前節點自己,也可以是佇列中的其他節點。例如上圖,節點0的jump指標指向了最後一個節點,而節點1的jump指標指向了它自己。這種連結串列有一個專門的名稱,叫Posting List.
要求,你設計一個演算法,複製給定的一個Posting List。演算法的時間複雜度是O(n), 演算法除了執行分配賦值節點所需的記憶體外,不能分配多餘記憶體,例如給定上面佇列,你的演算法智慧多分配五個節點的記憶體。演算法可以更改原佇列,當更改後,需要將佇列恢復原狀。
這道題目有一定難度,難點在於如何複製jump指標。如果沒有jump指標,那麼複製一個單項鍊表是很容易的。我們先設想一個最簡單的做法,先不考慮jump節點,先把單項佇列複製出來,然後再考慮如何設定新佇列節點的jump指標。
一個做法是,給定一個具體的節點,然後將佇列遍歷以便,判斷jump節點與當前節點的距離,例如給定節點0,我們通過遍歷得知,jump指向的節點,與當前節點有四個節點的距離,然後在新複製的佇列中,從新賦值的節點0往後走4個節點,找到新拷貝的節點4,接著把新節點0的jump指標指向新生成的節點4.
上面做法有個問題就是,設定每個節點的jump指標時,都得將佇列遍歷一次,這樣的話,整個演算法複雜度會是 O(n^2).但題目要求,演算法複雜度必須是O(n),由此,我們必須重新思考新的演算法。
我的做法是這樣的,首先遍歷佇列,為每個節點生成一個拷貝:
接著,把原節點的next指標指向對應的拷貝節點,拷貝節點的next指標指向原節點原來next指向的節點:
經過上面的變動,原節點跟它自己的拷貝連線了起來,同時原佇列的連線性任然得以保持,例如圖中,上面的節點0要抵達它的下一個節點,那麼只需要通過next指標到達它的拷貝節點,也就是下面的節點0,然後再通過拷貝節點的next指標就可以抵達上面的節點1了。
此時,新節點的jump指標就容易設定了,例如要設定新拷貝的節點0的jump指標,先通過它原節點的jump指標,找到節點4,然後再通過節點4的next指標,找到節點4的拷貝節點,也就是上圖下方的節點4,最後把拷貝節點0的 jump指標設定成對應的上圖下方的節點4即可。如果用node來表示原節點,cpNode來表示對應的拷貝節點,下面程式碼就可以設定拷貝節點的jump指標:
cpNode = node.next; //獲得拷貝節點
cpNode.jump = node.jump.next;
遍歷原佇列的每個節點,採取上面的操作,這樣,新拷貝節點的jump指標就能夠正確的設定了。
最後,恢復原佇列以及設定拷貝節點的next指標。由於拷貝節點的next指標,指向原節點原來next指向的物件,由此只要把原節點的next設定為拷貝節點的next指向的物件,就可以復原原來的狀態。拷貝節點0的next要想指向拷貝節點1,首先通過它自己的next,找到原節點1,也就是圖中上方的節點1,然後通過原節點1的next找到對應的拷貝節點,也就是圖中下方的節點1,於是把拷貝節點0的next指標就可以指向拷貝節點1,從而實現圖中下方的節點0通過next指標指向拷貝節點1。實現程式碼如下:
cpNode = node.next; //獲得拷貝節點
node.next = cpNode.next; //恢復原節點的next指標
node = node.next;
cpNode.next = node.next; //將當前拷貝節點的next指標指向下一個拷貝節點。
上面演算法,每一步驟的時間複雜度都是o(n),同時我們只為新節點分配記憶體,除此只為,並沒有多餘的記憶體分配,因此,演算法符合題目要求。我們看看具體的程式碼實現:
public class ListUtility {
private Node tail;
private Node head;
private int listLen = 0;
private int postingListLen = 0;
HashMap<Integer, PostingNode> map = new HashMap<Integer, PostingNode>();
PostingNode createPostingList(int nodeNum) {
if (nodeNum <= 0) {
return null;
}
postingListLen = nodeNum;
PostingNode postingHead = null, postingTail = null;
PostingNode postingNode = null;
int val = 0;
while (nodeNum > 0) {
if (postingNode == null) {
postingHead = new PostingNode();
postingHead.val = val;
postingNode = postingHead;
postingTail = postingHead;
} else {
postingNode.next = new PostingNode();
postingNode = postingNode.next;
postingNode.val = val;
postingNode.next = null;
postingTail = postingNode;
}
map.put(val, postingNode);
val++;
nodeNum--;
}
PostingNode tempHead = postingHead;
createJumpNode(tempHead);
return postingHead;
}
private void createJumpNode(PostingNode pHead) {
Random ra = new Random();
while (pHead != null) {
int n = ra.nextInt(postingListLen);
pHead.jump = map.get(n);
pHead = pHead.next;
}
}
public void printPostingList(PostingNode pHead) {
while (pHead != null) {
System.out.print("(node val:" + pHead.val + " jump val : " + pHead.jump.val + " ) ->");
pHead = pHead.next;
}
System.out.print(" null ");
}
....
}
上面的程式碼用於建立一個Posting list, 連結串列的複雜演算法實現在類PostingList.java中,程式碼如下:
public class PostingList {
private PostingNode head ;
private PostingNode copyHead;
public PostingList(PostingNode node) {
this.head = node;
}
public PostingNode copyPostingList() {
createPostingNodes();
createJumpNodes();
ajustNextPointer();
return copyHead;
}
private void createPostingNodes() {
PostingNode node = null;
PostingNode tempHead = head;
while (tempHead != null) {
node = new PostingNode();
node.next = tempHead.next;
node.val = tempHead.val;
tempHead.next = node;
tempHead = node.next;
}
}
private void createJumpNodes() {
PostingNode temp = head;
copyHead = temp.next;
while (temp != null) {
PostingNode cpNode = temp.next;
cpNode.jump = temp.jump.next;
temp = cpNode.next;
}
}
private void ajustNextPointer() {
PostingNode temp = head;
while (temp != null) {
PostingNode cpNode = temp.next;
temp.next = cpNode.next;
temp = temp.next;
if (temp != null) {
cpNode.next = temp.next;
} else {
cpNode.next = null;
}
}
}
}
createPostingNodes 執行的是步驟1,它為每一個節點生成一個拷貝。
createJumpNodes 執行步驟2,它為每一個拷貝節點設定他們對應的jump指標
ajustNextPointer 執行步驟3,它調整原佇列節點的next指標,以及設定拷貝節點的next指標。
每個函式裡,只包含一個while迴圈,用於遍歷佇列,因此演算法實現的複雜度是o(n).具體的程式碼講解和除錯演示過程,請參看視訊。
歡迎關注公眾號,讓我們一起學習,交流,成長:
相關推薦
面試演算法:雙指標單向連結串列的自我複製
有一種較為特殊的單向連結串列,如下: 這種連結串列一個特點是,除了next指向下一個節點外,它還多了一個指標jump,這個指標指向佇列中的某一個節點,這個節點可以是當前節點自己,也可以是佇列中的其他節點。例如上圖,節點0的jump指標指向了最後一個節點
11. 微軟面試題:輸入一個單向連結串列,輸出該連結串列中倒數第k個結點。連結串列的倒數第0個結點為連結串列的尾指標
題目:輸入一個單向連結串列,輸出該連結串列中倒數第k個結點。連結串列的倒數第0個結點為連結串列的尾指標。 分析: 單鏈表只能向後遍歷,不能向前遍歷,尾指標好找,倒數第K個不能從尾指標向前找。 倒的不好找,正的好找,我們只需要知道連結串列的總長度,就可以知道正數第幾個節點(
演算法——資料結構(單向連結串列的實現)
單向連結串列也叫單鏈表,是連結串列中最簡單的一種形式,它的每個節點包含兩個域,一個資訊域(元素域)和一個連結域。這個連結指向連結串列中的下一個節點,而最後一個節點的連結域則指向一個空值。 表元素域el
演算法:(三)連結串列
(一)連結串列和陣列都是一種線性結構 陣列是一段連續的儲存空間 連結串列空間不一定保證連續,為臨時分配 (二)連結串列的分類 按連線方向 單鏈表 雙鏈表 按有環無環 普通連結串列 迴圈連結串列 (三)連結串
程式設計師面試一百題-09-查詢單向連結串列中倒數第k個結點
1-題目 : 輸入一個單向連結串列,輸出該連結串列中倒數第k個結點,連結串列的倒數第0個結點為連結串列的尾指標。 2-思路 : 2.1-錯誤思路 : 為了得到倒數第k個結點,很自然的想法是先走到連結串列的尾端,再從尾端回溯k步,可是單向連結串列只有從前往後的指標而沒有從後往前的指標。
常見演算法:C語言中連結串列的操作(建立,插入,刪除,輸出)
連結串列中最簡單的一種是單向連結串列,它包含兩個域,一個資訊域和一個指標域。這個連結指向列表中的下一個節點,而最後一個節點則指向一個空值。 一個單向連結串列包含兩個值: 當前節點的值和一個指向下一個節點的連結 一個單向連結串列的節點被分成兩個部分。第一個部分儲存或者顯示關於
一天一演算法 day2--反向列印單向連結串列
題目 面試題5:輸入一個連結串列的頭節點,從尾到頭反過來打印出每個節點的值 題目分析 單向連結串列只能從前往後遍歷,給出頭節點,如果正向列印是很容易的一件事,如果反向列印,就相對麻煩了。因為單向連結串列的特性我們無法從後往前遍歷,那麼就需要一種“後進先出”的資料結構或者方法來
劍指Offer Java版 雙指標在連結串列中的應用
所謂雙指標,指的是在遍歷物件的過程中,不是使用單個指標進行訪問,而是使用兩個相同方向或者相反方向的指標進行遍歷,從而達到相應的目的。雙指標的使用可以降低程式的時間複雜度或者空間複雜度,總之是一種有效的解決問題的方案。 (注:這裡所說的指標,並不是
演算法與資料結構-單向連結串列的直接插入排序和快速排序
#include <stdio.h> #include <stdlib.h> //單向連結串列定義 typedef struct LNode { int data; struct LNode *next; }LNode; typedef LNode *LinkLis
演算法面試:單向連結串列節點的奇偶排序。
給定一個單項鍊表,要求實現一個演算法,把連結串列分成兩部分,前一部分全是下標為偶數的節點,後一部分全是下標為奇數的節點,例如給定連結串列為下圖的第一個佇列,要求編寫一個演算法,將連結串列轉換為第二個佇列: 要求演算法不能分配多餘的記憶體,同時,在操作連
面試演算法:連結串列成環的檢測
在有關連結串列的面試演算法題中,檢測連結串列是否有環是常見的題目。給定一個連結串列,要求你判斷連結串列是否存在迴圈,如果有,給出環的長度,要求演算法的時間複雜度是O(N), 空間複雜度是O(1). 如果大家對圖論有了解的話,那麼就會知道深度優先搜尋,在進行
結構與演算法(03):單向連結串列和雙向連結串列
本文原始碼:[GitHub·點這裡](https://github.com/cicadasmile/model-arithmetic-parent) || [GitEE·點這裡](https://gitee.com/cicadasmile/model-arithmetic-parent) # 一、連結串列
演算法入門題:如何反轉一個單向連結串列?
最近在 LeetCode 上面玩 ``連結串列`` 型別的題目,所以打算寫一篇文章,分享一下在做連結串列型別題目的心得。 眾所周知,玩連結串列就是玩指標,今天跟大家講解一個連結串列的入門題目,**如何反轉一個單向連結串列** 也是 LeetCode #206 是很熱門的一道程式設計題 [LC#206 R
對於單向連結串列的10幾種常用演算法
list.c檔案如下 #include "list.h" /*返回head連結串列POS節點的位置*/ LINK list_moov_pos(LINK head,int pos){ LINK node = head; while(--pos) node = node->pNe
《資料結構與演算法》之連結串列—單向連結串列
連結串列(LinkedList) 連結串列是一種物理儲存單元上非連續、非順序的儲存結構,資料元素的邏輯順序是通過連結串列中的指標連結次序實現的。連結串列由一系列節點(連結串列中每一個元素稱為節點)組成,節點可以在執行時動態生成。每個節點包括兩個部分:一個是儲存資料元素的資料域,另一個是儲存下一個
BZOJ4837:[Lydsy1704月賽]LRU演算法(雙指標&模擬)
Description 小Q同學在學習作業系統中記憶體管理的一種頁面置換演算法,LRU(LeastRecentlyUsed)演算法。 為了幫助小Q同學理解這種演算法,你需要在這道題中實現這種演算法,接下來簡要地介紹這種演算法的原理: 1.初始化時,你有一個最大長度為n
單向連結串列例項:終端互動簡易通訊錄
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 5 6 typedef struct Contacts_infomation{ 7
演算法---輸出單向連結串列中倒數第K個節點
輸出單向連結串列中倒數第K個節點,比如我們現在有int型別的1,2,3,4,5,6,7,8組成的一個單向連結串列,求倒數第三個元素。如圖所示: 我們正常的思路就是從後往前推倒數第K個元素,這裡有這樣幾個問題。首先是單向連結串列的限制,連結
【資料結構】帶有尾指標的連結串列:連結串列實現佇列
前面寫了用動態陣列實現佇列,現在我將用帶有尾指標的連結串列實現佇列。 我們知道佇列需要在兩端進行操作,如果是以普通連結串列進行底層實現的佇列,在入隊時需要找到最後一個節點才能進行新增元素,這樣相當於遍歷一遍連結串列,從而造成巨大的時間浪費。 為了解決這個問題,我們引入了尾
一步一步寫演算法 之單向連結串列
【 宣告:版權所有,歡迎轉載,請勿用於商業用途。 聯絡信箱:feixiaoxing @163.com】 有的時候,處於記憶體中的資料並不是連續的。那麼這時候,我們就需要在資料結構中新增一個屬性,這個屬性會記錄下面一個數據的地址。有了這個地址之後,所有的資料就像一條鏈子一