作為線性表的兩種儲存方式 —— 鍊錶和陣列,這對相愛相殺的好**有著各自的優缺點。接下來,我們梳理一下這兩種方式。
陣列,所有元素都連續的儲存於一段記憶體中,且每個元素占用的記憶體大小相同。這使得陣列具備了通過下標快速訪問資料的能力。
但連續儲存的缺點也很明顯,增加容量,增刪元素的成本很高,時間複雜度均為 o(n)。
增加陣列容量需要先申請一塊新的記憶體,然後複製原有的元素。如果需要的話,可能還要刪除原先的記憶體。
刪除元素時需要移動被刪除元素之後的所有元素以保證所有元素是連續的。增加元素時需要移動指定位置及之後的所有元素,然後將新增元素插入到指定位置,如果容量不足的話還需要先進行擴容操作。
總結一下陣列的優缺點:
優點:可以根據偏移實現快速的隨機讀寫。
缺點:擴容,增刪元素極慢。
鍊錶,由若干個結點組成,每個結點包含資料域和指標域。結點結構如下圖所示:
一般來講,鍊錶中只會有乙個結點的指標域為空,該結點為尾結點,其他結點的指標域都會儲存乙個結點的記憶體位址。鍊錶中也只會有乙個結點的記憶體位址沒有儲存在其他結點的指標域,該結點稱為頭結點。
鍊錶的儲存方式使得它可以高效的在指定位置插入與刪除,時間複雜度均為 o(1)。
在結點 p 之後增加乙個結點 q 總共分三步:
申請一段記憶體用以儲存 q (可以使用記憶體池避免頻繁申請和銷毀記憶體)。
將 p 的指標域資料複製到 q 的指標域。
更新 p 的指標域為 q 的位址。
刪除結點 p 之後的結點 q 總共分兩步:
將 q 的指標域複製到 p 的指標域。
釋放 q 結點的記憶體。
鍊錶的主要**
#include using namespace std;
//定義乙個結點模板
templatestruct node
node(const t &d) : data(d), next(nullptr) {}
};//刪除 p 結點後面的元素
templatevoid remove(node*p)
auto tmp = p->next->next;
delete p->next;
p->next = tmp;
}//在 p 結點後面插入元素
templatevoid insert(node*p, const t &data)
//遍歷鍊錶
templatevoid walk(node*p, const v &vistor)
}int main() );
cout << sum << endl;
remove(p);
sum = 0;
walk(p, [&sum](const node*p) -> void );
cout << sum << endl;
return 0;
}
class
listnode
listnode
(int val)
listnode
(int val, listnode next)
}
面試問題總結
無法高效獲取長度,無法根據偏移快速訪問元素,是鍊錶的兩個劣勢。然而面試的時候經常碰見諸如獲取倒數第k個元素,獲取中間位置的元素,判斷鍊錶是否存在環,判斷環的長度等和長度與位置有關的問題。這些問題都可以通過靈活運用雙指標來解決。
tips:雙指標並不是固定的公式,而是一種思維方式~
先來看"倒數第k個元素的問題"。設有兩個指標 p 和 q,初始時均指向頭結點。首先,先讓 p 沿著 next 移動 k 次。此時,p 指向第 k+1個結點,q 指向頭節點,兩個指標的距離為 k 。然後,同時移動 p 和 q,直到 p 指向空,此時 q 即指向倒數第 k 個結點。可以參考下圖來理解:
class solution
while(p != nullptr)
return q;
}};
獲取中間元素的問題。設有兩個指標 fast 和 slow,初始時指向頭節點。每次移動時,fast向後走兩次,slow向後走一次,直到 fast 無法向後走兩次。這使得在每輪移動之後。fast 和 slow 的距離就會增加一。設煉表有 n 個元素,那麼最多移動 n/2 輪。當 n 為奇數時,slow 恰好指向中間結點,當 n 為 偶數時,slow 恰好指向中間兩個結點的靠前乙個(可以考慮下如何使其指向後乙個結點呢?)。
下述**實現了 n 為偶數時慢指標指向靠後結點。
class solution
return p;
} };
是否存在環的問題。如果將尾結點的 next 指標指向其他任意乙個結點,那麼鍊錶就存在了乙個環。
一部分中,總結快慢指標的特性 —— 每輪移動之後兩者的距離會加一。下面會繼續用該特性解決環的問題。
當乙個鍊錶有環時,快慢指標都會陷入環中進行無限次移動,然後變成了追及問題。想象一下在操場跑步的場景,只要一直跑下去,快的總會追上慢的。當兩個指標都進入環後,每輪移動使得慢指標到快指標的距離增加一,同時快指標到慢指標的距離也減少一,只要一直移動下去,快指標總會追上慢指標。
根據上述表述得出,如果乙個鍊錶存在環,那麼快慢指標必然會相遇。實現**如下:
class solution
if(fast == slow)
slow = slow->next;
}return nullptr;
}};
後乙個問題,如果存在環,如何判斷環的長度呢?方法是,快慢指標相遇後繼續移動,直到第二次相遇。兩次相遇間的移動次數即為環的長度。 約瑟夫問題 迴圈鍊錶簡單解決
約瑟夫求生最初版本 在羅馬人占領喬塔帕特後,39 個猶太人與josephus及他的朋友躲到乙個洞中,39個猶太人決定寧願死也不要被敵人抓到,於是決定了乙個自殺方式,41個人排成乙個圓圈,由第1個人開始報數,每報數到第3人該人就必須自殺,然後再由下乙個重新報數,直到所有人都自殺身亡為止。然而josep...
演算法 鍊錶 用Js解決回文鍊錶問題(簡單易懂)
想要解決這一道演算法題,建議先把 leetcode 206.反轉鍊錶 以及 leetcode 141.環形鍊錶 這兩題做一做!對解決回文鍊錶這一題很有幫助。回文鍊錶的題目如下 請判斷乙個鍊錶是否為回文鍊錶。示例 1 輸入 1 2 輸出 false 示例 2 輸入 1 2 2 1 輸出 true 高階...
常見的鍊錶題目
一些常見的單鏈表題目,總結思路和實現 1.單鏈表的反序 2.給單鏈表建環 3.檢測單鏈表是否有環 4.給單鏈表解環 5.檢測兩條鍊錶是否相交 6.不輸入頭節點,刪除單鏈表的指定節點 只給定待刪除節點指標 1.單鏈表的反序 view plain 逆轉鍊錶,並返回逆轉後的頭節點 node reverse...