在筆試面試考數據結構時,因爲時間有限,所出的題不會是紅黑樹、平衡二叉樹等比較複雜的數據結構。鏈表結構簡單,題目規模小但須要仔細考慮細節,所以稱爲筆試面試中的高頻考點。所以,下面總結出鏈表相關題目,以供複習。面試
1.比較順序表和鏈表的優缺點,說說他們分別在什麼場景下使用?算法
2.從尾到頭打印單鏈表(劍指offer第五題)數據結構
3.刪除一個無頭單鏈表的非尾節點dom
4.在無頭單鏈表的一個非頭結點前插入一個節點指針
5.單鏈表實現約瑟夫環code
6.逆置/反轉單鏈表排序
7.單鏈表排序(冒泡排序&快速排序)遞歸
8.合併兩個有序鏈表合併後依然有序內存
9.查找鏈表的中間節點,要求只能遍歷一次鏈表io
10.查找單鏈表倒數第K個節點,要求只能遍歷一次鏈表
11.判斷單鏈表是否帶環?若帶環,求環的長度?求環的入口點?並計算每一個算法的時間複雜度&空間複雜度。
12.判斷兩個鏈表是否相交,若相交,求交點(假設鏈表不帶環)
13.判斷兩個鏈表是否相交,若相交,求交點(假設鏈表帶環)
14.複雜鏈表的複製,一個鏈表的每一個節點,有一個指向next指針指向下一個節點,還有一個random指針指向這個鏈表中的一個隨機節點或者NULL,如今要求實現複製這個鏈表
15.求兩個已排序鏈表中相同的數據。void UnionSet(ListNode* l1,ListNode* l2);
/////////////////////////////////////////////////////////////////////////////////////////////////////////(分割線)///////////////////////////////////////////////////
1.比較順序表和鏈表的優缺點,說說他們分別在什麼場景下使用?
首先咱們從順序表和鏈表的結構上來進行分析:
(1)對於順序表,不管是動態的仍是靜態的,他們都是連續的存儲空間,在讀取上時間效率比較高,可經過地址之間的運算來訪問,可是在插入和刪除時會出現比較麻煩的負載操做。
(2)對於順序表,由於是鏈式存儲。所以在咱們須要的時候咱們纔在堆上爲他們開闢空間,鏈表對於插入刪除比較簡單,可是遍歷的話須要屢次跳轉。
其次,咱們從順序表和鏈表的空間申請方式來看:
(1)對於順序表,空間開闢是在順序表已滿的時候開闢,開闢次數較多的時候會出現較大的空間浪費
(2)對於鏈表,空間是針對單個節點的,不存在多餘的空間浪費。而且在碎片內存池的機制下,能夠有效的利用空間。
綜上所述:順序表通常用於查找遍歷操做比較頻繁的狀況下使用,鏈表則針對於數據刪除修改操做比較多的狀況下使用。
2.從尾到頭打印單鏈表
從尾到頭打印單鏈表有兩種解法,一種是利用棧把節點從頭至尾push進去,利用棧先進後出的特色,從尾到頭打印單鏈表節點,一種是利用遞歸,在輸出現有節點以前輸出下一個節點,循環直至最後一個節點,而後再將節點從尾到頭依次打印。
code1:利用棧
void PrintTailToHead(ListNode* head)
{
stack<int> st;
ListNode* p = head;
while (p != NULL)
{
st.push(p->_data);
p = p->_next;
}
while (!st.empty())
{
printf("%d->", st.top());
st.pop();
}
}
code2:利用遞歸
void PrintTailToHead(ListNode* head)
{
if (head != NULL)
{
while (head->_next != NULL)
{
PrintTailToHead(head->_next);
}
}
printf("%d->", head->data);
}
3.刪除一個無頭單鏈表的非尾節點
因爲鏈表無頭,因此用常規方法刪除節點是不可能的。因此咱們能夠換種思路,將要刪除的節點後面的節點的值賦給要刪的節點,而後再把要刪除節點的後面的節點刪除,等於經過轉換,爲被刪除節點創造了一個頭結點。代碼以下:
void DeleteNotTailNode(ListNode* p)
{
ListNode* s = p->_next;
assert(s);
p->_data = s->_data;
p->_next = s->_next;
free(p);
}
4.在無頭單鏈表的一個非頭結點前插入一個節點
這個題目跟上一個題目很像。在這個非頭結點後面插入一個節點,把這個非頭節點的值賦給新插入的節點,而後再把要插入的值賦給這個非頭節點便可。
void InsertNotHeadNode(ListNode* p, int data)
{
ListNode* s = (ListNode)malloc(sizeof(&ListNode));
assert(s);
s->_next =p->_next;
p->_next = s;
s->_data = p->_data;
p->_data = data;
}
5.單鏈表實現約瑟夫環(劍指offer第45題)
6.逆置/反轉單鏈表(劍指offer第16題)
7.單鏈表排序(冒泡排序&快速排序)
8.合併兩個有序鏈表合併後依然有序(劍指offer第17題)
這個題比較簡單,分別用指針指向兩個鏈表,比較兩個鏈表指針所指向節點的值,而後將節點取下來從新組成一個鏈表便可,代碼以下:
ListNode Merge(ListNode* head1, ListNode* head2)
{
if (head1 == NULL)
return head2;
if (head2 == NULL)
return head1;
ListNode* newhead = NULL;
if (head1->_data < head2->_data)
{
newhead = head1;
newhead->_next=Merge(head1->_next, head2);
}
if (head1->_data>head2->data)
{
newhead = head2;
newhead->_next = Merge(head1, head2->_next);
}
}
9.查找鏈表的中間節點,要求只能遍歷一次鏈表
查找鏈表的中間節點,但只能遍歷一次鏈表,因此咱們會想到用快慢指針來解決這個問題。定義一個快指針,每次走兩步,載定義一個慢指針,每次走一步。等到快指針走到鏈表尾,慢指針所指向的節點就是鏈表的中間節點。代碼以下:
ListNode* FindMidNode(ListNode* head)
{
ListNode* fast;
ListNode* slow;
fast = head;
slow = head;
while (fast&&fast->_next)
{
slow = slow->_next;
fast = fase->_next->_next;
}
retrun slow;
}
10.查找單鏈表倒數第K個節點,要求只能遍歷一次鏈表(劍指offer第15題)
其實這個題跟上面的題很像,稍微轉化一下就能想出思路。咱們能夠定義兩個指針,一個指針先走K步,而後兩個指針同時移動,等到先走的指針走到鏈表尾部,後走的指針所指向的節點就是倒數第K個節點。要注意考慮鏈表的各類狀況。代碼以下:
ListNode* FindKthNode(ListNode* head,int k)
{
if (head == NULL || k == 0)
return NULL;
ListNode* fast;
ListNode* slow;
fast = head;
slow = head;
for (int i = 0; i < k - 1; ++i) //要注意鏈表長度比K短的狀況
{
if (fast->_next != NULL)
fast = fast->_next;
else retrun NULL;
}
while (fast->_next != NULL)
{
fast = fast->_next;
slow = slow->_next;
}
return slow;
}
11.判斷單鏈表是否帶環?若帶環,求環的長度?求環的入口點?並計算每一個算法的時間複雜度&空間複雜度。(劍指offer第56題)
12.判斷兩個鏈表是否相交,若相交,求交點(假設鏈表不帶環)
13.判斷兩個鏈表是否相交,若相交,求交點(假設鏈表帶環)
14.複雜鏈表的複製,一個鏈表的每一個節點,有一個指向next指針指向下一個節點,還有一個random指針指向這個鏈表中的一個隨機節點或者NULL,如今要求實現複製這個鏈表(劍指offer第26題)
15.求兩個已排序鏈表中相同的數據。void UnionSet(ListNode* l1,ListNode* l2);
16.在已排序的鏈表中刪除鏈表中重複的結點(劍指offer第57題)