資料結構: 連結串列問題總結
阿新 • • 發佈:2019-01-02
資料結構: 連結串列問題總結
刷了幾個連結串列的題目,做一下總結.
題目來自 <程式設計師程式碼面試指南>
連結串列本身操作靈活,程式碼量少.很適合作為面試題考查.
連結串列的基本操作,如增刪改查.
本文用到連結串列用的是牛客網上的結構.如下所示:
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :val(x), next(NULL) {
}
};
連結串列的題目中遇到的:
-
快慢指標
用來找節點,比如中間的節點.倒數第幾個節點.
判斷連結串列是否有環,找環的入口.
-
頭結點(哨兵節點)
:拼接連結串列時可以使用.比如將合併兩個有序連結串列.
-
翻轉連結串列的操作
翻轉連結串列,注意儲存
next
指標域. 且一個節點一個節點做. -
尾插法
和頭插法
用於生成目標的連結串列. 將劃分的連結串列接起來. 注意:拼成的連結串列末尾指向NULL.
-
還有其他一些待補充.
題目: 列印兩個連結串列的公共部分
/* 題目: 列印兩個有序單鏈表的公共部分. 公共部分指的是相等的值 */ vector<int> printCommonPart(ListNode* head1, ListNode* head2) { vector<int> res; if (head1 == NULL || head2 == NULL) return res; ListNode* p1 = head1; ListNode* p2 = head2; while (p1 != NULL&&p2 != NULL) { if (p1->val == p2->val) { res.push_back(p1->val); p1 = p1->next; p2 = p2->next; } else if (p1->val < p2->val) { p1 = p1->next; } else if (p1->val > p2->val) { p2 = p2->next; } } return res; }
題目: 在單鏈表和雙鏈表中刪除倒數第K個節點.
- 通過計數,找到倒數第K個節點的前驅
- 刪除操作,單鏈表和雙鏈表有區別
// 在單鏈表中刪除節點,需要知道前驅 /* 有三種情況: 連結串列長度N,倒數第K個 1) N<K.則返回空, 2) N==K,則需要返回第一個節點 3) N>K,需要兩次遍歷,1) 遍歷完,每次k-- 再一次從頭遍歷,每次k++,知道k==0.即找到順數N-K個節點 */ ListNode* RemoveLastKthNode(ListNode* &head, int lastKth) { if (head == NULL) return NULL; int k = lastKth; ListNode* cur = head; // 第一遍遍歷 while (cur != NULL) { cur = cur->next; k--; } if (k == 0) { return head->next; } else if (k > 0) { return NULL; } else { // k<0 cur = head; // 使用++k找到的是倒數Kth的前驅. // 若使用k++,則找到的是倒數Kth節點 while (++k != 0) { cur = cur->next; } ListNode* tobeDelete = cur->next; cur->next = tobeDelete->next; delete tobeDelete; tobeDelete = NULL; return head; } }
DoubleLinkNode* RemoveLastKthNode(DoubleLinkNode* head, int lastKth) {
if (head==NULL) {
return NULL;
}
DoubleLinkNode* cur = head;
int k = lastKth;
while (cur != NULL) {
cur = cur->next;
k--;
}
if (k == 0) {
return head->next;
}
else if (k > 0) {
return NULL;
}
else {
cur = head;
// 使用++k找到的是倒數Kth的前驅.
// 若使用k++,則找到的是倒數Kth節點
while (++k != 0) {
cur = cur->next;
}
// 找到了前驅
DoubleLinkNode* tobedeleted = cur->next;
DoubleLinkNode* next = tobedeleted->next;
cur->next = next;
next->last = cur;
delete tobedeleted;
tobedeleted = NULL;
return head;
}
}
題目: 翻轉單鏈表中的一部分
- 找到 m的前驅,找到n的後繼.
- 翻轉 [m…n] 這個區間的連結串列
- 注意邊界情況.
ListNode* reverseBetween(ListNode* head, int m, int n) {
if (head == NULL || n <= m) {
return head;
}
int len = 0;
ListNode* cur = head;
ListNode* beforeM = NULL;
ListNode* M = NULL;
ListNode* N = NULL;
ListNode* afterN = NULL;
while (cur != NULL) {
len++;
beforeM = len == m - 1 ? cur : beforeM;
M = len == m ? cur : M;
N = len == n ? cur : N;
afterN = len == n + 1 ? cur : afterN;
cur = cur->next;
}
ListNode* res = head; // 返回的節點
// reverse
cur = M;
ListNode* pre = beforeM;
while (cur != afterN&&cur != NULL) {
ListNode* next = cur->next;
cur->next = pre;
pre = cur;
cur = next;
}
M->next = afterN;
if (beforeM == NULL) {
res = N;
} else {
beforeM->next = N;
}
return res;
}
題目: 將單鏈表按某值劃分成為左邊小中間相等右邊大的形式
方法1: 將單鏈表放進去陣列中,進行 類似於快排的 partition
操作,最後呢,將排序後的節點連線起來.
/*
方法一:
1. 連結串列的值放進陣列中,
2. 對陣列進行partition
3. 將陣列中的連結串列重接
時間複雜度O(N)
空間複雜度O(N)
且不能保證資料保持和輸入連結串列一樣的順序
*/
void ArrPartition(vector<ListNode*> &v, int pivot) {
int size = v.size();
if (size == 0) {
return;
}
int small = -1;
int big = v.size();
int index = 0;
while (index < v.size()) {
if (v[index]->val < pivot) {
v[++small] = v[index++];
}
else if (v[index]->val > pivot) {
swap(v[--big], v[index]);
}
else {
// 當前這個值和pivot相等
index++;
}
}
}
ListNode* LinkListPartition1(ListNode* head,int pivot) {
if (head == NULL)
return NULL;
vector<ListNode*> v;
// 把連結串列放進陣列
ListNode* cur = head;
while (cur != NULL) {
v.push_back(cur);
cur = cur->next;
}
// 對陣列進行partition
cur = head;
ArrPartition(v, pivot);
for (int i = 1; i < v.size(); i++) {
v[i - 1]->next = v[i];
}
v[v.size() - 1]->next = NULL;
return v[0];
}
方法2: 建立三個連結串列,分別是 小於,等於,大於最後將三個連結串列進行連線
/*
方法2:
使用類似建立連結串列的方法,
建立三個連結串列,分別是 小於,等於,大於
最後將三個連結串列進行連線
時間複雜度O(N)
空間複雜度O(1)
能保持順序性
*/
ListNode* LinkListPartition2(ListNode* head, int pivot) {
if (head == NULL)
return NULL;
ListNode* smallStart = NULL;
ListNode* smallEnd = NULL;
ListNode* equalStart = NULL;
ListNode* equalEnd = NULL;
ListNode* bigStart = NULL;
ListNode* bigEnd = NULL;
ListNode* cur = head;
ListNode* next = NULL;
while (cur!=NULL) {
next = cur->next;
cur->next = NULL; // 將每個節點都斷開.就會保證每個單獨的連結串列最後是指向空的
// 尾插法
if (cur->val < pivot) {
if (smallStart == NULL) {
smallStart = cur;
smallEnd = cur;
}
else {
smallEnd->next = cur;
smallEnd = cur;
}
}
else if (cur->val == pivot) {
if (equalStart == NULL) {
equalStart = cur;
equalEnd = cur;
}
else {
equalEnd->next = cur;
equalEnd = cur;
}
}
else {
if (bigStart == NULL) {
bigStart = cur;
bigEnd = cur;
} else {
bigEnd->next = cur;
bigEnd = cur;
}
}
cur = next;
}
// 把小於和等於連線
if (smallStart != NULL) {
smallEnd->next = equalStart;
// 如果等於部分為空,則將等於部分的
equalEnd = equalEnd == NULL ? smallEnd : equalEnd;
}
// 將全部連線
if (equalEnd != NULL) {
equalEnd->next = bigStart;
}
return smallStart != NULL ? smallStart : equalStart != NULL ? equalStart : bigStart;
}
題目: 將搜尋二叉樹轉換成為雙向連結串列
方法1: 利用棧進行中序排序,最後將結果儲存是佇列中,再遍歷佇列,進行拼接.
/*
將搜尋二叉樹轉換為雙向連結串列
*/
// 利用棧實現中序遍歷,儲存在佇列中,逐一節點進行連線.注意頭結點和末尾節點特殊處理
TreeNode* convert1(TreeNode* head) {
TreeNode* res = NULL;
TreeNode* end = NULL;
queue<TreeNode*> q;
TreeNode* cur = head;
stack<TreeNode*> s;// 利用一個棧實現中序遍歷
while (!s.empty() || !cur != NULL) {
while (cur != NULL) {
s.push(cur);
cur = cur->left;
}
if (!s.empty()) {
cur = s.top();
s.pop();
q.push(cur);
cur = cur->right;
}
}
// 中序遍歷的結果都存在佇列中了
while (!q.empty()) {
cur = q.front();
q.pop();
if (res == NULL) {
res = cur;
res->left = NULL;
end = cur;
}
else {
cur->left = end;
end->right = cur;
end = cur;
}
}
if(end!=NULL)
end->right = NULL;
return res;
}
方法2:利用遞迴函式.
/*
利用遞迴函式,
將整個二叉進行轉換,
返回都是雙向連結串列的尾節點,且,尾節點的right指向頭結點.
*/
TreeNode* convertProcess(TreeNode* head) {
if (head == NULL) {
return NULL;
}
TreeNode* leftTreeLast = convertProcess(head->left);
TreeNode* rightTreeLast = convertProcess(head->right);
TreeNode* leftTreeFirst = leftTreeLast != NULL ? leftTreeLast->right : NULL;
TreeNode* rightTreeFirst = rightTreeLast != NULL ? rightTreeLast->right : NULL;
if (leftTreeLast != NULL&&rightTreeLast != NULL) {
leftTreeLast->right = head;
head->left = leftTreeLast;
head->right = rightTreeFirst;
rightTreeFirst->left = head;
rightTreeLast->right = leftTreeFirst;
return rightTreeLast;
}
else if(leftTreeLast!=NULL){
leftTreeLast->right = head;
head->right = leftTreeFirst;
head->left = leftTreeLast;
return head;
}
else if (rightTreeLast != NULL) {
head->right = rightTreeFirst;
rightTreeFirst->left = head;
rightTreeLast->right = head;
return rightTreeLast;
}
else {
head->right = head;
return head;
}
}
TreeNode* convert2(TreeNode* bst) {
if (bst == NULL) {
return NULL;
}
TreeNode* last = convertProcess(bst);
// 最後把尾節點之指向頭結點的指標取消掉了
TreeNode* first = last->right;
last->right = NULL;
return first;
}
題目: 在O(1)時間刪除連結串列節點
/*
只給一個單鏈表的節點Node,讓刪除這個節點
*/
void DeleteNode(ListNode* node) {
if (node == NULL) {
return ;
}
ListNode* next = node->next;
if (next == NULL) {
throw "ERROR";
}
node->val = next->val;
node->next = next->next;
return ;
}