最近想学习一波数据结构与算法,之前也有学习过部分,但是感觉时间久了会忘记的差不多了,而且学习的不是很系统,就是自己突然想到某个知识点就去学习,也有在leetcode上刷题,也是久了就忘记了,自己写的代码就看不懂了,但是觉得数据结构和算法挺重要的,业余时间也没有别的事情做,就再啃啃喽。
觉得知识点看了,懂了如何实现,但是自己用代码实现起来还是有点吃力,总是有问题,基础太差了。刚开始花费的时间很长,不过虽然自己基础差学起来耗时,但是看到代码通过的那一刻还是开心的,觉得值得,也可能代码是自己想了好久才写出来的,还存在问题,也可能是百思不得其解看了大佬们的思路才写出来的,也可能看了大佬们的分享也还是费九牛二虎之力才解决,不论怎样,也算是个进步,为了加深印象以及自己记录一些东西方便后续复习,就打算写点博客。
今天学习链表,碰到了回文串的问题,刚开始不知道怎么去想,单链表又不能首尾同时处理来比较节点内容,然后想起链表其他问题的快慢指针(慢的跑一个节点,快的跑两个节点),然后思路大概就有了。
遍历链表,找到中间节点,在遍历的同时将前半部分逆序,然后将链表分两个小链表,同时遍历两个小链表,比较节点内容。在实现的时候对于链表节点个数为奇数和偶数的情况是不一样处理的,奇数的话就用中间把中间节点去掉(不比较),然后开始比较,偶数的话用当前节点作为第二段链表的首节点,当前节点的前驱节点作为第一段链表的头节点,比较两个链表即可。
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
题目描述:
请判断一个链表是否为回文链表。
示例 1:
输入: 1->2 输出: false
示例 2:
输入: 1->2->2->1 输出: true
进阶:
你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
在开始写的时候,要反转链表,单链表一般需要三个指针,一个用来标识当前操作的节点,一个用来标识当前节点的前驱,一个用来标识当前节点的后继节点(因为是单链表,如果在改变当前节点的next指针之前不保存后继节点,那么就找不到后边的链表了),考虑到将头节点单独处理,写了代码:
1 /** 2 * Definition for singly-linked list. 3 * struct ListNode { 4 * int val; 5 * ListNode *next; 6 * ListNode(int x) : val(x), next(NULL) {} 7 * }; 8 */ 9 class Solution { 10 public: 11 bool isPalindrome(ListNode* head) { 12 if (NULL == head) 13 return true; 14 15 //寻找链表的中间节点,并在此过程中将前半部分逆序 16 ListNode* pLowCurr = NULL;//当前操作节点,也是即将找到的中间节点 17 ListNode* pPre = NULL; 18 ListNode* pNext = NULL; 19 ListNode* pFast = NULL; 20 21 pLowCurr = pFast = head; 22 23 //如果快指针到达链表尾部,那么慢指针就处于链表的中间位置 24 while (pFast && pFast->next) 25 { 26 //反转前半部分链表 27 if (pLowCurr == head) 28 { 29 pPre = pLowCurr; 30 pNext = pLowCurr->next; 31 pPre->next = NULL; 32 33 pLowCurr = pNext; 34 pNext = pNext->next; 35 pFast = pNext; 36 } 37 else 38 { 39 pLowCurr->next = pPre; 40 pPre = pLowCurr; 41 pLowCurr = pNext; 42 pNext = pLowCurr->next; 43 //pFast = pNext->next; 44 pFast = pFast->next->next; 45 } 46 } 47 48 if (pFast == NULL) 49 { 50 //偶数个节点 51 pLowCurr->next = pNext; 52 } 53 else 54 { 55 //奇数个节点 56 pLowCurr = pNext; 57 } 58 59 //比较两段链表是否相等 60 while (pPre && pLowCurr) 61 { 62 if (pPre->val != pLowCurr->val) 63 { 64 return false; 65 } 66 pPre = pPre->next; 67 pLowCurr = pLowCurr->next; 68 } 69 return true; 70 } 71 };
后来在出问题分析的时候发现代码比较不友好,重复的代码太多了,然后就考虑把头节点和其余节点一起考虑,发现也是可以的,也可能是自己基础实在太差了,对于链表的操作觉得思路很清晰了,可是代码实现起来还是有漏洞,不断的在纸上画,然后逐语句分析,还是比较容易找问题的,发现自己可能脑子反应慢点,逻辑思维不太好,还是纸上画画比较清楚点,然后整理了下,代码如下:
1 /** 2 * Definition for singly-linked list. 3 * struct ListNode { 4 * int val; 5 * ListNode *next; 6 * ListNode(int x) : val(x), next(NULL) {} 7 * }; 8 */ 9 class Solution { 10 public: 11 bool isPalindrome(ListNode* head) { 12 if (NULL == head) 13 return true; 14 15 //寻找链表的中间节点,并在此过程中将前半部分逆序 16 ListNode* pLowCurr = NULL;//当前操作节点,也是即将找到的中间节点 17 ListNode* pPre = NULL; 18 ListNode* pNext = NULL; 19 ListNode* pFast = NULL; 20 21 pLowCurr = pFast = head; 22 23 //如果快指针到达链表尾部,那么慢指针就处于链表的中间位置 24 while (pFast && pFast->next) 25 { 26 //超时优化代码 27 pFast = pFast->next->next;//快指针先跑起来 28 pNext = pLowCurr->next;//保存后继节点 29 pLowCurr->next = pPre;//更新后继节点 30 pPre = pLowCurr;//更新前驱节点 31 32 pLowCurr = pNext;//更新当前节点 33 } 34 35 if (pFast != NULL) 36 { 37 //奇数个节点 38 pLowCurr = pLowCurr->next; 39 } 40 41 //比较两段链表是否相等 42 while (pPre && pLowCurr) 43 { 44 if (pPre->val != pLowCurr->val) 45 { 46 return false; 47 } 48 pPre = pPre->next; 49 pLowCurr = pLowCurr->next; 50 } 51 return true; 52 } 53 };
在实现的时候出现了两个问题:
1、在处理完原链表之后,比较两个小链表的时候,只顾着比较,没有移动当前操作节点,看到时间超过限制,不够仔细,没发现指针问题,以为是时间复杂度大于O(n),可是分析遍历链表复杂度O(n),遍历比较小链表O(n/2),总体来说还是O(n),后来才发现,自己实在是太粗心了。哎。。。
2、跑的快(走两个节点)的指针依赖于其他指针,导致不好处理,想着只要跑得快的指针有了初值,即指向头节点,那么无论怎么情况,都会比其余的指针跑的快,也就是说在原链表节点被改变之前就已经跑过这个节点了,所以,通过自己next来遍历就好了,但是要在头节点next指针被改变之前就操作。
写的代码效率还不是很高,希望能通过记录博客多学点东西把,加油!