想了很久,一道Microsoft的笔试题目 —— Reversing Linked List

Reversing Linked List

Given a constant K and a singly linked list L, you are supposed to reverse the links of every K elements on L. For example, given L being 1→2→3→4→5→6, if K=3, then you must output 3→2→1→6→5→4; if K=4, you must output 4→3→2→1→5→6.

Input Specification:

Each input file contains one test case. For each case, the first line contains the address of the first node, a positive N (≤) which is the total number of nodes, and a positive K (≤) which is the length of the sublist to be reversed. The address of a node is a 5-digit nonnegative integer, and NULL is represented by -1.

Then N lines follow, each describes a node in the format:

Address Data Next

where Address is the position of the node, Data is an integer, and Next is the position of the next node.

Output Specification:

For each case, output the resulting ordered linked list. Each node occupies a line, and is printed in the same format as in the input.

Sample Input:

00100 6 4
00000 4 99999
00100 1 12309
68237 6 -1
33218 3 00000
99999 5 68237
12309 2 33218

Sample Output:

00100 6 4
00000 4 99999
00100 1 12309
68237 6 -1
33218 3 00000
99999 5 68237
12309 2 33218

 

我的思路

  姥姥(陈越)说这道题是微软某年的笔试题目,还说不难。说实话,这道题我做了两天,想了有8个多小时(是我太菜了)。主要是一直习惯了链表,结果来了个静态链表,脑子一时转不过来。前面一直在用暴力的解法,直到后面通过不断的思考,才慢慢找到了思路。自己做出了,发现确实也不是很难。虽然花了很多时间,让我很痛苦,不过还好自己没有放弃这题,做了出来。只能说自己平时做得题太少了,脑子没有动起来。

  回到这个题目。题目的大意是,第一行输入静态链表首个节点的地址,静态链表的节点的个数N,每次连续反转节点的个数K。然后下面N行输入当前节点的地址Address,当前节点存放的数据Data,和它的下一个节点地址Next。然后会对连续的每K个节点进行反转,最后输出反转后的结果。比如, 有1→2→3→4→5→6, 如果 K=3, 那么应该输出3→2→1→6→5→4。如果K=4, 那么应该输出4→3→2→1→5→6。

  这道题我看了其他人的解法,基本上都是申请100000大小的结构体数组,然后把当前节点的地址作为数组的下标,这样子基本上就可以把静态链表当作普通的链表来做了。

  主要是我一开始没有想到这种做法,而且申请一个大小为100000的数组,让我感到很震惊。我觉得申请这么大的空间太浪费了,虽然题目的内存限制为64 MB,所以没有用这种方法,而是想出了别的解法。

  对于这一题,我写了4份不同的代码。

  一开始我觉得难的地方在于很难找到下一个节点的数组下标,所以直接通过遍历整个数组来找下一个节点的数组下标,然后再根据K来改变next的值。还有,当时为了找到反转后的最后一个节点应该指向的那个节点也花了不少时间,程序编得很复杂。如果有N个节点,那么光是遍历都要N2次。然后最后的输出也是通过遍历N2次。虽然最后这个程序通过了一些样本点,但当N取最大值105时,很明显是通过不了的。这个程序就不展示了。

  接下来是第2份,开始对这道题目有一点思路了。就是当N组的数据输入完后,我再申请一个大小为N的结构体数组,通过遍历,使节点按照地址顺序来存放。同时在这个过程中,通过STL里面的reverse函数来对每K个节点进行反转。最后再输出。不过在按照顺序来存放节点的过程中,我还是用遍历N2次的方法来找下一个节点的数组下标。这导致我还是运行超时,不过只有取最大N这一个样本点没通过,但解决了多余节点不在链表上的问题。下面给出第2份的代码:

 1 #include <cstdio>
 2 #include <algorithm>
 3 
 4 struct Node {
 5     int address;
 6     int data;
 7     int next;
 8 };
 9 
10 int main() {
11     int firstAddress, n, k;
12     scanf("%d %d %d", &firstAddress, &n, &k);
13     
14     Node node[n];
15     int pre;
16     for (int i = 0; i < n; i++) {
17         scanf("%d %d %d", &node[i].address, &node[i].data, &node[i].next);
18         if (node[i].address == firstAddress) pre = i;
19     }
20     
21     Node sqNode[n];     // 顺序表,把每一个节点按顺序存储到这个数组中,可以很方便找到下一个节点的数组下标,同时也很方便反转 
22     sqNode[0] = node[pre];
23     int cnt = 1;        // 用来记录连续节点的个数,防止出现多余的节点,遍历输出的时候用cnt 
24     
25     for (int i = 1; i < n && node[pre].next != -1; i++) {
26         for (int j = 0; j < n; j++) {
27             if (node[j].address == node[pre].next) {
28                 sqNode[i] = node[j];
29                 pre = j;
30                 cnt++;
31                 break;
32             }
33         }
34         
35         if (cnt % k == 0) std::reverse(sqNode + cnt - k, sqNode + cnt); // 每K个节点就反转 
36     }
37     
38     for (int i = 0; i < cnt; i++) {
39         printf("%05d %d ", sqNode[i].address, sqNode[i].data);
40         if (i < cnt - 1) printf("%05d\n", sqNode[i + 1].address);
41         else printf("-1\n");
42     }
43     
44     return 0;
45 }

  这个代码部分正确,只是当N很大时,就会运行超时,提交结果如下(一开始的提交记录丢了,所以又故意提交了第2份代码):

  然后我想了改进的算法,就是在输入N组数据的同时,就把输入的当前节点的地址Address按从小到大的顺序存放到一个数组中,我选择了插入排序。当把数据都输入完后,N个节点就按它的地址Address从小到大排好在数组中。然后再申请一个大小为N的数组,这个时候就通过二分查找来查找下一个节点的地址从而找到下一个节点的数组下标,再把这些节点按照按节点地址顺序排好在另外一个数组中,并且在排序的过程中通过reverse函数来对排好序的每K个节点进行反转。这大大提高了效率,最后也通过了全部的样本点!

  这是第3份程序的代码:

 1 #include <cstdio>
 2 #include <algorithm>
 3 
 4 struct Node {
 5     int address;
 6     int data;
 7     int next;
 8 };
 9 
10 void insertSort(Node *node, int n);
11 int binarySearch(Node *node, int n, int keyAddress, int &cnt);
12 
13 int main() {
14     int firstAddress, n, k;
15     scanf("%d %d %d", &firstAddress, &n, &k);
16     
17     Node node[n];
18     for (int i = 0; i < n; i++) {
19         scanf("%d %d %d", &node[i].address, &node[i].data, &node[i].next);
20         insertSort(node, i);
21     }
22     
23     Node sqNode[n];
24     int pre, cnt = 0;
25     pre = binarySearch(node, n, firstAddress, cnt);
26     sqNode[0] = node[pre];
27     
28     for (int i = 1; i < n && node[pre].next != -1; i++) {
29         pre = binarySearch(node, n, node[pre].next, cnt);
30         sqNode[i] = node[pre];
31         
32         if (cnt % k == 0) std::reverse(sqNode + cnt - k, sqNode + cnt);
33     }
34     
35     for (int i = 0; i < cnt; i++) {
36         printf("%05d %d ", sqNode[i].address, sqNode[i].data);
37         if (i < cnt - 1) printf("%05d\n", sqNode[i + 1].address);
38         else printf("-1\n");
39     }
40     
41     return 0;
42 }
43 
44 void insertSort(Node *node, int n) {
45     Node t = node[n];
46     int i = n - 1;
47     for ( ; i >= 0 && t.address < node[i].address; i--) {
48         node[i + 1] = node[i];
49     }
50     node[i + 1] = t;
51 }
52 
53 int binarySearch(Node *node, int n, int keyAddress, int &cnt) {
54     int left = 0, right = n - 1, mid;
55     while (left <= right) {
56         mid = (left + right) / 2;
57         if (keyAddress < node[mid].address) {
58             right = mid - 1;
59         }
60         else if (keyAddress > node[mid].address) {
61             left = mid + 1;
62         }
63         else {
64             cnt++;
65             return mid;
66         }
67     }
68 }

  第4份和第3份的代码其实没有什么不同,只不过把插入排序换成了STL里面的sort函数,一样可以通过。

 1 #include <cstdio>
 2 #include <algorithm>
 3 
 4 struct Node {
 5     int address;
 6     int data;
 7     int next;
 8 };
 9 
10 bool cmp(const Node &a, const Node &b);
11 int binarySearch(Node *node, int n, int keyAddress, int &cnt);
12 
13 int main() {
14     int firstAddress, n, k;
15     scanf("%d %d %d", &firstAddress, &n, &k);
16     
17     Node node[n];
18     for (int i = 0; i < n; i++) {
19         scanf("%d %d %d", &node[i].address, &node[i].data, &node[i].next);
20     }
21     
22     std::sort(node, node + n, cmp);    // 变化的地方,输入完数据后再用sort函数来排序 
23     
24     Node sqNode[n];
25     int pre, cnt = 0;
26     pre = binarySearch(node, n, firstAddress, cnt);
27     sqNode[0] = node[pre];
28     
29     for (int i = 1; i < n && node[pre].next != -1; i++) {
30         pre = binarySearch(node, n, node[pre].next, cnt);
31         sqNode[i] = node[pre];
32         
33         if (cnt % k == 0) std::reverse(sqNode + cnt - k, sqNode + cnt);
34     }
35     
36     for (int i = 0; i < cnt; i++) {
37         printf("%05d %d ", sqNode[i].address, sqNode[i].data);
38         if (i < cnt - 1) printf("%05d\n", sqNode[i + 1].address);
39         else printf("-1\n");
40     }
41     
42     return 0;
43 }
44 
45 bool cmp(const Node &a, const Node &b) {
46     return a.address < b.address;
47 }
48 
49 int binarySearch(Node *node, int n, int keyAddress, int &cnt) {
50     int left = 0, right = n - 1, mid;
51     while (left <= right) {
52         mid = (left + right) / 2;
53         if (keyAddress < node[mid].address) {
54             right = mid - 1;
55         }
56         else if (keyAddress > node[mid].address) {
57             left = mid + 1;
58         }
59         else {
60             cnt++;
61             return mid;
62         }
63     }
64 }

 

一些感触

  这道题目算是我到目前为止,所花时间最多的一道题目。不愧是大厂的笔试题目,我这种菜鸟花很长时间才勉强做了出来。也侧面看出我平时见过的题目比较少。自己是非科班,平时有空就抽点时间来自学编程,学到现在还不到1年,估计了一下大概9个月左右。在这个过程中我体会到编程带来的乐趣,也对各种高效的算法感到十分敬佩。要走编程这条路肯定是要吃苦的,但编程是我的兴趣,我只会一直走下去,学越来越多的计算机知识,永远也不会放弃!还有,再过一两个月我就参加计算机的转专业考核了,希望这次能够成功,圆我的编程之梦。

Tags: