1. 程式人生 > >資料結構: 連結串列問題總結

資料結構: 連結串列問題總結

資料結構: 連結串列問題總結

刷了幾個連結串列的題目,做一下總結.

題目來自 <程式設計師程式碼面試指南>

連結串列本身操作靈活,程式碼量少.很適合作為面試題考查.

連結串列的基本操作,如增刪改查.

本文用到連結串列用的是牛客網上的結構.如下所示:

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 ;
}

參考文獻

連結串列問題總結1

連結串列問題總結2