剑指offer(专项突破版)4.4 反转链表
分析
对于每一个要转换其next指针的结点,需要保存它的前驱和后继,先让它的next指向前驱,然后再向后移动。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
class Solution { public: ListNode* reverseList(ListNode* head) { ListNode *prev = NULL; ListNode *cur = head; while(cur){ ListNode *next = cur->next; cur->next = prev; prev = cur; cur = next; } return prev; } };
|
变量cur指向当前遍历到的节点,变量prev指向当前节点的前一个节点,而变量next指向下一个节点。每遍历一个节点之后,都让变量prev指向该节点。在遍历到尾节点之后,变量prev最后一次被更新,因此,变量prev最终指向原始链表的尾节点,也就是反转链表的头节点。
显然,上述代码的时间复杂度是O(n),空间复杂度是O(1)。
分析
把两个链表反转,再相加,得到的和链表再反转。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
|
class Solution { public: ListNode* reverseList(ListNode* head){ ListNode *prev = NULL; ListNode *cur = head; while(cur){ ListNode *next = cur->next; cur->next = prev; prev = cur; cur = next; } return prev; } ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) { ListNode *num1 = reverseList(l1); ListNode *num2 = reverseList(l2); ListNode *node = new ListNode(0); ListNode *res = node; int carry = 0; while(num1 && num2){ int sum = num1->val + num2->val + carry; carry = sum / 10; ListNode *cur = new ListNode(sum % 10); res->next = cur; num1 = num1->next; num2 = num2->next; res = res->next; } while(num1){ int sum = num1->val + carry; carry = sum / 10; ListNode *cur = new ListNode(sum % 10); res->next = cur; num1 = num1->next; res = res->next; } while(num2){ int sum = num2->val + carry; carry = sum / 10; ListNode *cur = new ListNode(sum % 10); res->next = cur; num2 = num2->next; res = res->next; } if(carry){ ListNode *cur = new ListNode(1); res->next = cur; } node = node->next; res = reverseList(node); return res; } };
|
- 时间复杂度:O(max(m, n))。
- 空间复杂度:需要使用额外的空间来存储反转后的两个链表和最终的结果链表,它们的长度分别是 max(m, n),因此空间复杂度为 O(max(m, n))。

分析
把链表分成两部分,后半部分逆转,再分别与前半部分的结点连接起来。要找到中间结点,可以使用快慢指针。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
|
ListNode* findMidNode(ListNode *head){ ListNode *slow = head->next; ListNode *fast = slow->next; while(fast->next){ slow = slow->next; fast = fast->next; if(fast->next){ fast = fast->next; } else{ break; } } return slow; } ListNode* reverseList(ListNode *head){ ListNode *cur = head; ListNode *prev = NULL; while(cur){ ListNode *next = cur->next; cur->next = prev; prev = cur; cur = next; } return prev; }
class Solution { public: void reorderList(ListNode* head) { if(!head->next || !head->next->next){ return; } ListNode *dummy = new ListNode(0); dummy->next = head; ListNode *mid = findMidNode(head); mid = reverseList(mid); ListNode *prev = dummy; ListNode *first = head; ListNode *second = mid; while(first && second && first != second){ ListNode *fnext = first->next; ListNode *snext = second->next; prev->next = first; first->next = second; prev = second; first = fnext; second = snext; } } };
|
- 时间复杂度:
- 找到中间节点:采用快慢指针法,时间复杂度为 O(n),其中 n 是链表的长度。
- 反转后半部分链表:采用迭代方式,时间复杂度为 O(n),其中 n 是后半部分链表的长度。
- 合并两个链表:在合并过程中,每次迭代都会连接一个节点,因此时间复杂度也是 O(n),其中 n 是链表的长度。
- 空间复杂度:O(1)

分析
和LCR026的思路是一样的,先找到中间结点,再把后半部分进行反转,然后比较是否相同。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
|
class Solution { public:
ListNode* findMidNode(ListNode *head){ ListNode *slow = head; ListNode *fast = head->next; while(fast->next){ slow = slow->next; fast = fast->next; if(fast->next){ fast = fast->next; } } return slow; } ListNode* reverseList(ListNode *head){ ListNode *cur = head; ListNode *prev = NULL; while(cur){ ListNode *next = cur->next; cur->next = prev; prev = cur; cur = next; } return prev; }
bool isPalindrome(ListNode* head) { if(!head->next){ return true; } ListNode *mid = findMidNode(head); ListNode *back = reverseList(mid->next); ListNode *fore = head; while(fore && back){ if(fore->val != back->val){ return false; } fore = fore->next; back = back->next; } return true; } };
|

总结
- 反转链表操作:在反转链表时,需要使用三个指针来记录当前节点、前驱节点和后继节点,然后依次更新它们的指针,实现链表的反转。这个过程需要注意保存链表的头结点和边界条件的处理。
- 利用快慢指针找中间节点:在某些情况下,需要找到链表的中间节点,可以利用快慢指针的方法。快指针每次移动两步,慢指针每次移动一步,当快指针到达链表尾部时,慢指针则指向链表的中间节点。