1. 程式人生 > >劍指offer練習-連結串列

劍指offer練習-連結串列

由於最近太忙,幾個連結串列的題目都寫在了一個解決方案裡了,程式碼可能有點亂,以下先提交程式碼,思路都在註釋裡,後期有時間了再整理筆記

 

  1 // printListFromTailToHead.cpp: 定義控制檯應用程式的入口點。
  2 //
  3 
  4 #include "stdafx.h"
  5 #include<iostream>
  6 #include<vector>
  7 #include<queue>
  8 #include<set>
  9 #include<stack>
 10 #include<malloc.h>
 11 #include<map>
 12 #include<debugapi.h>
 13 using namespace std;
 14 
 15 #define N 40
 16 struct ListNode {
 17 	int val;
 18 	struct ListNode *next;
 19 
 20 };
 21 struct RandomListNode {//複雜連結串列
 22 	int label;
 23 	struct RandomListNode *next, *random;
 24 	RandomListNode(int x) :
 25 		label(x), next(NULL), random(NULL) {
 26 	}
 27 };
 28 class Solution {
 29 public:
 30 	vector<int> printListFromTailToHead(ListNode *head) {//將連結串列逆序列印
 31 		/*思路
 32 		通常,這種情況下,我們不希望修改原連結串列的結構。返回一個反序的連結串列,這就是經典的“後進先出”,我們可以使用棧實現
 33 		這種順序。每經過一個結點的時候,把該結點放到一個棧中。
 34 		當遍歷完整個連結串列後,再從棧頂開始逐個輸出結點的值,給一個新的連結串列結構,這樣連結串列就實現了反轉。*/
 35 		stack<int> nodes;
 36 		ListNode *node = head;
 37 		vector<int> result;
 38 		while (node != NULL) {
 39 			nodes.push(node->val);
 40 			node = node->next;
 41 		}
 42 		while (!nodes.empty()) {
 43 			result.push_back(nodes.top());
 44 			nodes.pop();
 45 		}
 46 		return result;
 47 	}
 48 	ListNode* CreateListNode(int num, int nodes[]) {//建立連結串列
 49 		ListNode *p, *q;
 50 		ListNode *listnode;
 51 		listnode = (ListNode *)malloc(sizeof(ListNode));//分配空間,也可以用c++的new
 52 		listnode->next = NULL;
 53 		p = listnode;
 54 		for (int i = 0; i<num; i++) {
 55 			q = (ListNode *)malloc(sizeof(ListNode));
 56 			q->val = nodes[i];
 57 			p->next = q;
 58 			p = q;
 59 		}
 60 		p->next = NULL;
 61 		return listnode;
 62 	}
 63 	ListNode* CreateListNodeWithHead(int num, int nodes[]) {//建立連結串列
 64 		ListNode *p, *q;
 65 		ListNode *listnode;
 66 		listnode = (ListNode *)malloc(sizeof(ListNode));//分配空間,也可以用c++的new
 67 		//listnode->next = NULL;
 68 		p = listnode;
 69 		for (int i = 0; i<num; i++) {
 70 			q = (ListNode *)malloc(sizeof(ListNode));
 71 			q->val = nodes[i];
 72 			p->next = q;
 73 			p = q;
 74 		}
 75 		listnode = listnode->next;//這裡將頭結點指向下一個結點,是為了使頭結點數值有意義,注意與上一個建立方法的程式碼的兩處區別
 76 		p->next = NULL;
 77 		return listnode;
 78 	}
 79 	void PrintListNode(ListNode *listnode) {//遍歷輸出連結串列
 80 		ListNode *p = listnode->next;//因為建立的連結串列頭結點數值為空,所以要從頭結點下一個結點開始列印
 81 		//cout <<"test"<< listnode->val<<"test";
 82 		while (p!= NULL) {
 83 			cout << p->val;
 84 			p = p->next;
 85 		}
 86 		cout << endl;
 87 	}
 88 	void PrintReverseListNode(ListNode *listnode) {//遍歷輸出逆置後的連結串列,因為逆置之後的頭結點數值非空,所以要從頭結點開始列印
 89 		ListNode *p = listnode;
 90 		//cout << "test" << listnode->val << "test";
 91 		while (p->next!= NULL) {//在逆置的時候,逆置後的連結串列的尾結點是原連結串列的頭結點,所以輸出的時候不要把尾結點輸出
 92 			cout << p->val;
 93 			p = p->next;
 94 		}
 95 		cout << endl;
 96 	}
 97 	ListNode* FindKthToTail(ListNode *plistnode,int k) {//找到連結串列中倒數第k個元素
 98 		/*思路
 99 		我們可以定義兩個指標。第一個指標從連結串列的頭指標開始遍歷向前走k-1,第二個指標保持不動;從第k步開始,
100 		第二個指標也開始從連結串列的頭指標開始遍歷。由於兩個指標的距離保持在k-1,
101 		當第一個(走在前面的)指標到達連結串列的尾結點時,第二個指標(走在後面的)指標正好是倒數第k個結點。*/
102 		if (plistnode == NULL || k ==0) {
103 			return NULL;
104 		}
105 		ListNode *palistnode;
106 		ListNode *pblistnode;
107 		palistnode = plistnode;
108 		pblistnode = plistnode;
109 		for (int i = 0; i < k-1; i++) {
110 			if(palistnode->next != NULL) {
111 				palistnode = palistnode->next;
112 			}
113 			else {
114 				return NULL;
115 			}
116 		}
117 		while (palistnode->next != NULL) {
118 			palistnode = palistnode->next;
119 			pblistnode = pblistnode->next;
120 		}
121 		return pblistnode;
122 	}
123 	ListNode* ReverseList(ListNode *head) {//將連結串列逆置
124 		/*思路
125         這個很簡單,我們使用三個指標,分別指向當前遍歷到的結點、它的前一個結點以及後一個結點。
126         在遍歷的時候,做當前結點的尾結點和前一個結點的替換。
127 		*/
128 		ListNode *Cur, *Nex, *Pre;
129 		if (head == NULL)
130 			return NULL;
131 		if (head->next == NULL)
132 			return head;
133 		Cur = head;
134 		Nex = NULL;
135 		Pre = NULL;
136 		while (Cur != NULL) {
137 			Nex = Cur->next;
138 			Cur->next = Pre;
139 			Pre = Cur;
140 			Cur = Nex;
141 		}
142 		return Pre;
143 	}
144 	ListNode* Merge(ListNode *listnode1, ListNode *listnode2) {//合併兩個有序連結串列,還可以使用遞迴演算法合併兩個有序連結串列
145 
146 		if (listnode1== NULL)
147 			return listnode2;
148 		if (listnode2 == NULL)
149 			return listnode1;
150 		ListNode *p = listnode1->next;//因為這兩個連結串列建立的時候頭結點數值為空,所以要從頭結點下一個結點開始遍歷
151 		ListNode *q = listnode2->next;
152 		ListNode *result;
153 		ListNode *t;
154 		result = (ListNode *)malloc(sizeof(ListNode));
155 		result->next = NULL;
156 		ListNode *temp = result;
157 		while (p != NULL&&q != NULL) {
158 			if (p->val < q->val) {
159 				t= (ListNode *)malloc(sizeof(ListNode));
160 				t->val = p->val;
161 				temp->next = t;
162 				temp = t;
163 				p = p->next;
164 			}
165 			else {
166 				t = (ListNode *)malloc(sizeof(ListNode));
167 				t->val = q->val;
168 				temp->next = t;
169 				temp = t;
170 				q = q->next;
171 			}
172 		}
173 		while (p != NULL) {
174 			t = (ListNode *)malloc(sizeof(ListNode));
175 			t->val = p->val;
176 			temp->next = t;
177 			temp = t;
178 			p = p->next;
179 		}
180 		while (q != NULL) {
181 			t = (ListNode *)malloc(sizeof(ListNode));
182 			t->val = q->val;
183 			temp->next = t;
184 			temp = t;
185 			q = q->next;
186 		}
187 		temp->next = NULL;
188 		return result;
189 	}
190 	ListNode* MergeWithRecursion(ListNode *listnode1, ListNode *listnode2) {//遞迴合併兩個有序連結串列
191 		if (listnode1 == NULL)
192 			return listnode2;
193 		if (listnode2 == NULL)
194 			return listnode1;
195 		ListNode *listnode =NULL;
196 		if (listnode1->val < listnode2->val) {
197 			listnode = listnode1;
198 			listnode->next = MergeWithRecursion(listnode1->next, listnode2);
199 		}
200 		else {
201 			listnode = listnode2;
202 			listnode->next = MergeWithRecursion(listnode1, listnode2->next);
203 		}
204 		return listnode;
205 	}
206 	RandomListNode* CloneWithMap(RandomListNode *pHead) {//複製複雜的連結串列:節點可以有兩個指向,一個指向下一節點,一個指向隨機節點
207 		/*
208 		   方法一思路:首先複製原連結串列普通指標域,一次遍歷即可,複製過程中將新舊連結串列中的節點一一對應,將其對映關係放入到
209 		   map<RandomListNode *, RandomListNode *> nodeMap中,然後複製隨機指標域,先找到隨機指標指向的隨機節點,由於map中對應的有該隨機節點對應的
210 		   新節點,所以也就找到了隨機指標指向的結點對應的新節點,該新節點即為新連結串列前結點對應的隨機指標指向的結點,然後新節點隨機指標指向該節點,時間複雜度:O(N)  
211 		*/
212 		if (pHead = NULL) {
213 			return NULL;
214 		}
215 		map<RandomListNode *, RandomListNode *> nodeMap;
216 		RandomListNode *currNode = pHead;
217 		RandomListNode *newHead = NULL, *preNode = NULL, *newNode = NULL;
218 		//首先複製原連結串列的普通指標域,一次遍歷即可完成
219 		while (currNode != NULL) {
220 			newNode = new RandomListNode(currNode->label);
221 			nodeMap[currNode] = newNode;
222 			if (preNode == NULL) {
223 				newHead = newNode;
224 			}
225 			else
226 			{
227 				preNode->next = newNode;
228 			}
229 			preNode = newNode;
230 		}
231 		//  接著複製隨機指標域, 需要兩次遍歷
232 		currNode = pHead;
233 		newNode = newHead;
234 		while (currNode != NULL&&newNode != NULL) {
235 			RandomListNode *randNode = currNode->random;
236 			RandomListNode *newRandNode = nodeMap[randNode];
237 			newNode->random = newRandNode;//這裡找到原連結串列對應的隨機指標指向的結點後,再從map中找到對應的新節點,然後將新連結串列隨機指標指向該新節點
238 			currNode = currNode->next;
239 			newNode = newNode->next;
240 		}
241 		return newHead;
242 	}
243 	RandomListNode* CloneTogether(RandomListNode *pHead) {
244 		/*
245 		  我們需要的就只是能夠建立新節點與原節點之前的對應關係就可以了, 連結串列中的順序非隨機訪問方式,能夠很簡單的通過接單的nex指標t域查詢下一個節點
246 		  ,那麼我們將新節點直接插入到原結點的後面,這樣可以很方便的通過原來節點找到新節點,
247 		  1.遍歷一遍原始連結串列,複製結點N對應的N’,將其插入到結點N的後面
248 		  2.確定每個隨機指標的指向,只需遍歷一遍連結串列即可確定每個結點的隨機指標的指向
249 		  次遍歷一遍,將原始連結串列和複製連結串列分開,奇數為原始連結串列,偶數為複製連結串列
250 		*/
251 		if (pHead = NULL) {
252 			return NULL;
253 		}
254 		RandomListNode *currNode = pHead;
255 		RandomListNode *newHead = NULL, *preNode = NULL, *newNode = NULL;
256 		while (currNode != NULL) {
257 			if ((newNode = new RandomListNode(currNode->label)) == NULL) {
258 				perror("new error:");
259 				exit(-1);
260 			}
261 			// 將新的節點newNode連線在currNode的後面
262 			newNode->next = currNode->next;
263 			currNode->next = newNode;
264 			//  指向指向下一個節點
265 			currNode = newNode->next;
266 		}
267 		//  接著複製隨機指標域,原來節點的下一個位置就是其對應的新節點
268 		currNode = pHead;
269 		newNode = currNode->next;
270 		while (currNode != NULL) {
271 			RandomListNode *randNode = currNode->random;//  隨機指標域randNode
272 			RandomListNode *newNode = currNode->next;
273 			if (randNode != NULL) {
274 				newNode->random = randNode->next;//新節點的隨機指標指向的節點即為舊節點的隨機指標指向的節點指向的下一個節點
275 			}
276 			else
277 			{
278 				newNode->random = NULL;
279 			}
280 			currNode = newNode->next;//  連結串列同步移動
281 		}
282 		// 將連結在一起的新舊兩個連結串列拆分開,脫鏈,更新各連結串列的next指標
283 		currNode = pHead;
284 		newNode = newHead = pHead->next;
285 		while (currNode != NULL) {
286 			currNode->next = newNode->next;
287 			if (newNode->next != NULL) {
288 				newNode->next = newNode->next->next;
289 			}
290 			else {
291 				newNode->next = NULL;
292 			}
293 			currNode = currNode->next;
294 			newNode = newNode->next;
295 		}
296 		return newHead;
297 	}
298 	void printReverse() {//用於使用者互動來轉置連結串列
299 		int num;
300 		//ListNode *listnode;//要建立的連結串列
301 		int listnodes[N];
302 		cout << "請輸入連結串列大小:" << endl;
303 		cin >> num;
304 		cout << "請依次輸入連結串列每個節點數值:" << endl;
305 		for (int i = 0; i<num; i++) {
306 			cin >> listnodes[i];
307 		}
308 		Solution solution;
309 		ListNode *listnode = solution.CreateListNode(num, listnodes);
310 		solution.PrintListNode(listnode);
311 		ListNode *result = solution.ReverseList(listnode);
312 		solution.PrintReverseListNode(result);
313 	}
314 	void printMerge() {//用於使用者互動有序連結串列合併過程
315 		Solution solution;
316 		int num1, num2;
317 		ListNode *listnode1, *listnode2;
318 		int listnodes1[N], listnodes2[N];
319 		ListNode *result2;
320 		cout << "請輸入第一個連結串列大小:" << endl;
321 		cin >> num1;
322 		cout << "請按照數值遞增順序依次輸入第一個連結串列每個節點數值:" << endl;
323 		for (int i = 0; i<num1; i++) {
324 			cin >> listnodes1[i];
325 		}
326 		//Solution solution;
327 		listnode1 = solution.CreateListNode(num1, listnodes1);
328 		solution.PrintListNode(listnode1);
329 		cout << "請輸入第二個連結串列大小:" << endl;
330 		cin >> num2;
331 		cout << "請按照數值遞增順序依次輸入第二個連結串列每個節點數值:" << endl;
332 		for (int i = 0; i<num2; i++) {
333 			cin >> listnodes2[i];
334 		}
335 		listnode2 = solution.CreateListNode(num2, listnodes2);
336 		solution.PrintListNode(listnode2);
337 		cout << "合併兩個有序連結串列" << endl;
338 		//如果使用遞迴呼叫的話要把連結串列移到頭結點的額下一個結點,不然會把頭結點的空值也合併進去了
339 		listnode1 = listnode1->next;
340 		listnode2 = listnode2->next;
341 		result2 = solution.MergeWithRecursion(listnode1, listnode2);
342 		solution.PrintReverseListNode(result2);
343 	}
344 	void printprintListFromTailToHead() {//使用者互動從尾到頭列印連結串列過程
345 		int num;
346 		//ListNode *listnode;//要建立的連結串列
347 		int listnodes[N];
348 		cout << "請輸入連結串列大小:" << endl;
349 		cin >> num;
350 		cout << "請依次輸入連結串列每個節點數值:" << endl;
351 		for (int i = 0; i<num; i++) {
352 			cin >> listnodes[i];
353 		}
354 		Solution solution;
355 		ListNode *listnode = solution.CreateListNode(num, listnodes);
356 		solution.PrintListNode(listnode);
357 		vector<int> result;
358 		result = solution.printListFromTailToHead(listnode);
359 		cout << endl << "逆序輸出該連結串列" << endl;
360 		for (int i = 0; i < num; i++) {
361 			cout << result[i];
362 		}
363 		cout << endl;
364 	}
365 	void printFindKthToTail() {//使用者互動列印倒數第K個值
366 		int num;
367 		//ListNode *listnode;//要建立的連結串列
368 		int listnodes[N];
369 		cout << "請輸入連結串列大小:" << endl;
370 		cin >> num;
371 		cout << "請依次輸入連結串列每個節點數值:" << endl;
372 		for (int i = 0; i<num; i++) {
373 			cin >> listnodes[i];
374 		}
375 		Solution solution;
376 		ListNode *listnode = solution.CreateListNode(num, listnodes);
377 		solution.PrintListNode(listnode); 
378 		int k;
379 		cout << "請輸入K值" << endl;
380 		cin >> k;
381 		ListNode *result = solution.FindKthToTail(listnode, k);
382 		cout << result->val;
383 	}
384 };
385 int main() {
386 	/*int num;
387 	//ListNode *listnode;//要建立的連結串列
388 	int listnodes[N];	
389 	cout << "請輸入連結串列大小:" << endl;
390 	cin >> num;
391 	cout << "請依次輸入連結串列每個節點數值:" << endl;
392 	for (int i = 0; i<num; i++) {
393 		cin >> listnodes[i];
394 	}
395 	Solution solution;
396 	ListNode *listnode=solution.CreateListNode(num, listnodes);
397 	solution.PrintListNode(listnode);*/
398 	Solution solution;
399 	solution.printReverse();
400 }