支线任务-1
题目:LeetCode No. 234 Palindrome Linked List(回文链表)
题目大意:
给定一个单向链表,试确定它是不是一个回文链表。
以及要求使用O(N)的时间复杂度以及O(1)的空间复杂度。
其实就回文这个问题来说,可以有一万种题目来难道很多人(比如最长回文子串,公共最长回文子串之类的问题)。
不过如果只是像本题这样判断是不是回文的话应该算是非常容易的一件事情。
而题目要求的O(1)的额外空间复杂度也没什么难事,因为真正在比较的时候无非也就需要两个游标。但这就又要求两个游标要对称的前进,也就是说单向链表是做不到的。这也就很自然的带出了我们的想法——让一半的节点的指向反转,所以直接了断的说,这个题目实际上是个反转链表的题目。
大致就是将上图的上面的链表改成下面的样子,然后通过从最中间的两个节点(如果一共有奇数个节点的话其实最中间的一个可以不考虑,然后就变成偶数个了)向两侧同时遍历来判断。
由于整个算法每一步都是线性的,所以O(N)的时间复杂度和O(1)的空间复杂度都已经满足了。那么接下来的问题就是如何将常数最优化。
我的方法是在第一遍遍历的同时设置两个游标,其中一个是另外一个速度的一半,前面的游标一直走到结束为止,这样可以知道一共多少个节点;后面的游标每经过一个节点就改变那个节点的指向方向。
这样在第一个游标结束的时候第二个游标正好会在正中间(如果是奇数个还需要再进一步)并且同时整个链表也反转成适当的样子了,这样只需第二遍遍历就可以出结果了,这样算来常数大约是2.5倍。事实证明这样的算法在所有AC程序中也是最快的之一(当然由于服务器压力大不稳定所以每次测出来时间都可能会有略微的不同)。
下面附上程序
1 class Solution { 2 public: 3 bool isPalindrome(ListNode* head) { 4 if (head == NULL || head->next == NULL) 5 return true; 6 ListNode *first, *second, *third = NULL, *temp; 7 first = second = head; 8 int cnt = 1; 9 while (first->next != NULL) 10 { 11 first = first->next; 12 ++cnt; 13 if (!(cnt & 1) && cnt != 2) 14 { 15 temp = second; 16 second = second->next; 17 temp->next = third; 18 third = temp; 19 } 20 } 21 temp = second; 22 second = second->next; 23 temp->next = third; 24 third = temp; 25 if (cnt & 1) 26 { 27 second = second->next; 28 } 29 while (second != NULL) 30 { 31 if (third->val ^ second->val) 32 { 33 return false; 34 } 35 else 36 { 37 second = second->next; 38 third = third->next; 39 } 40 } 41 return true; 42 } 43 };
那么做完一道题是不是就完事了呢?并不是,我们要能够举一反三。
下面给出几个类似的题目(同样是LeetCode上的题目):
一:No. 143 Reorder List
题目大意:给定单向链表 L: L0→L1→…→Ln-1→Ln,
要求将其重排: L0→Ln→L1→Ln-1→L2→Ln-2→…
解法:和这道题相似,也是要在遍历的时候留一个半速游标同步反转,这样在结束的时候得到两个反向半个链表然后重新遍历连接。
二:No. 61 Rotate List
题目大意:给定单向链表链表L: L0→L1→…→Ln-1→Ln和正整数k,
要求将其重排为: Lk+1→Lk+2→…→Ln→L1…→Lk
解法:这个题目看似和反转没关系,但是可以通过将整个字符串反转再把两段分别反转得到结果。
三:No. 25 Reverse Node in K-Groups
题目大意:将一个链表以k个为单位,每个单位分别反转。如1->2->3->4->5->NULL, k == 2时会得到 2->1->4->3->5->NULL。
解法:方法类似,后面的游标每次走过k个节点就需要把生成的链接回来。
感兴趣的同学可以自己去实现以下这几道题目 : )