文章首發於:github.com/USTB-musion…前端
今天來聊一聊前端面試中很是基礎的兩種數據結構——「數組」和「鏈表」。git
先看下幾個常見的面試題:github
你能夠先思考一下如何回答上邊的問題🤔,而後帶着答案來閱覽接下來的內容。面試
在聊這個問題以前,先看一下數據從邏輯結構上的分類。主要分爲兩類:線性表和非線性表。數組
線性表: 數據連成一條線的結構,今天要聊的鏈表和數組就屬於這一類,除此以外還有棧,隊列等。bash
非線性表: 數據之間的關係是非線性的,如樹,堆,圖等。數據結構
看完線性表和非線性表以後,來繼續看下:ui
數組的定義:數據是一種線性表數據結構,它用一組連續的內存空間,來存儲一組具備相同類型的數據。spa
因爲數組在內存中是連續存放的,因此經過下標來隨機訪問數組中的元素效率是很是高的。但與此同時,爲了保證連續性,若是想在數組中添加一個元素,須要大量地對數據進行搬移工做。同理想在數組中刪除一個元素也是如此。因此咱們得出一個結論:在數組中隨機訪問的效率很高,可是執行添加和刪除時效率低下,平均時間複雜度爲O(n)。指針
鏈表的定義: 是一種物理存儲單元上非連續、非順序的存儲結構,數據元素的邏輯順序是經過鏈表中的指針連接次序實現的。
剛纔介紹了在數組中添加和刪除一個元素的效率是低下的。而鏈表的存儲空間是不連續的,使用鏈表添加或者刪除一個數據,咱們並不須要爲了保持內存的連續性而對數據進行搬移,因此在鏈表中添加和刪除元素是很是高效的。但萬事都有兩面性,正由於鏈表的存儲空間是不連續的,想要在鏈表中訪問一個元素時,就沒法像數組同樣根據首地址和下標,經過尋址公式來計算出對應的節點。而只能經過指針去依次遍歷找出相應的節點。因此咱們得出一個結論:在鏈表中執行添加和刪除操做時效率很高,而隨機訪問的效率很低,平均時間複雜度爲O(n)。
經過前邊內容的介紹,經過一個表格來直觀看下兩種在時間複雜度的差別:
時間複雜度 | 數組 | 鏈表 |
---|---|---|
添加 | O(n) | O(1) |
刪除 | O(n) | O(1) |
隨機訪問 | O(1) | O(n) |
因此回答下鏈表和數組的區別:
1.內存組織方式不一樣
2.添加,刪除,插入的時間複雜度不一樣
3.鏈表支持動態擴容
示例:
輸入: 1->2->3->4->5->NULL
輸出: 5->4->3->2->1->NULL
複製代碼
設置三個變量來分別表示當前節點和當前節點的先後節點,翻轉後便可。
var reverseList = function(head) {
let pre = null;
for (let cur = head; cur;) {
let nextTemp = cur.next; // 保存當前頭節點的下一個節點
cur.next = pre;
pre = cur;
cur = nextTemp;
}
return pre;
};
複製代碼
給定一個包含 n 個整數的數組nums,判斷nums中是否存在三個元素 a,b,c ,使得a + b + c = 0 ?找出全部知足條件且不重複的三元組。
例如, 給定數組 nums = [-1, 0, 1, 2, -1, -4],
知足要求的三元組集合爲:
[
[-1, 0, 1],
[-1, -1, 2]
]
複製代碼
1.對數組進行排序,若是數組的第一個元素大於0或者最後一個元素小於0,則不可能出現和爲0的狀況;
2.首先選定一個元素(A),再利用雙指針對數組的元素進行遍歷,將一個指針指向A元素的後一個元素(B),另外一個指針指向數組的最後一個元素(C);
3.判斷B+C的結果是不是A的相反數,若是B+C > (-A) ,則使C指針向前移動,若是B+C <(-A) ,則使B指針向後移動;
4.若是B指針的元素與其前一個元素相等,則B指針向後移動一位;若是C指針與其後一個元素相等,則C指針向前移動一位;
5.重複以上步驟
實現代碼以下:
var threeSum = function(nums) {
//用來存取最後結果集
let result = new Array();
//頭指針
let head;
//尾指針
let end;
//固定值
let fixedVal;
//排序
nums.sort((a, b) => {
return a-b;
});
//判斷數組內元素是否都爲整數或負數,直接返回
if(nums[0] > 0 || nums[nums.length - 1] < 0) return result;
// 開始遍歷
for (let i = 0; i < nums.length; i++) {
//固定值
fixedVal = nums[i];
// 若是先後元素相同,跳過這次循環(固定值)
if(fixedVal === nums[i-1]) continue;
//一開始的固定值爲nums[0],因此頭指針爲 i+1 下一個元素
head = i+1;
//尾指針
end = nums.length - 1;
//若是頭指針小於尾指針元素
while(head < end){
//判斷固定值+頭指針+尾指針是否等於0
if(nums[head] + nums[end] + fixedVal === 0){
//聲明數組,存放這三個值
let group = new Array();
group.push(nums[head]);
group.push(nums[end]);
group.push(fixedVal);
result.push(group);
//存放完畢以後,不要忘記頭指針和尾指針的移動(不然會產生死循環)
head += 1;
end -= 1;
//若是頭指針知足小於尾指針且移動後的指針和移動前的指針元素相等,再往前移動
while(head < end && nums[head] === nums[head - 1]){
head += 1;
}
//若是頭指針知足小於尾指針且移動後的指針和移動前的指針元素相等,再日後移動
while(head < end && nums[end] === nums[end + 1]){
end -= 1;
}
//小於 0 須要移動頭指針,由於嘗試着讓數據比原有數據大一點
}else if(nums[head] + nums[end] + fixedVal < 0){
head++;
}else{
//不然,尾指針向前移動,讓數據小於元數據
end--;
}
}
}
return result;
};
複製代碼
給定一個鏈表,刪除鏈表的倒數第 n 個節點,而且返回鏈表的頭結點。 示例:
給定一個鏈表: 1->2->3->4->5, 和 n = 2.
當刪除了倒數第二個節點後,鏈表變爲 1->2->3->5.
複製代碼
咱們可使用兩個指針而不是一個指針。第一個指針從列表的開頭向前移動 n+1 步,而第二個指針將從列表的開頭出發。如今,這兩個指針被 n個結點分開。咱們經過同時移動兩個指針向前來保持這個恆定的間隔,直到第一個指針到達最後一個結點。此時第二個指針將指向從最後一個結點數起的第n個結點。咱們從新連接第二個指針所引用的結點的 next 指針指向該結點的下下個結點。
var removeNthFromEnd = function(head, n) {
let first = head; // 慢指針
for (let i = 0; i < n; i++) {
first = first.next;
}
if (!first) return head.next; // 當鏈表長度爲n時,刪除第一個節點
let second = head; // 快指針
while (first.next) {
first = first.next;
second = second.next;
}
second.next = second.next.next;
return head;
};
複製代碼