每日算法题【链表】:链表分割、链表的回文结构
(11)链表分割
-
[链表分割_牛客题霸_牛客网]:
-
解题思路:
- 一个原链表,两个新链表
- 遍历原链表,为了防止因为维护原链表(保留比x大的节点并维持顺序)而进行复杂操作,我们直接选择创建2个新链表进行大节点和小节点的维护
- 比x大的我们尾插到大链表,比x小的我们尾插到小链表
注意:
- 正确初始化尾指针:在第一次添加节点时,同时设置头指针和尾指针
- 断开原链表连接:在将节点添加到新链表前,先保存下一个节点,然后断开当前节点的连接,避免形成循环链表
- 处理空链表情况:如果小链表为空,直接返回大链表
- 统一处理方式:对大小链表的处理采用相同的模式,提高代码一致性
/* struct ListNode {int val;struct ListNode *next;ListNode(int x) : val(x), next(NULL) {} };*/class Partition { public:ListNode* partition(ListNode* pHead, int x) {// 定义两个新的空链表(头节点和尾节点)ListNode* BigList = nullptr;ListNode* BigCur = nullptr;ListNode* SmallList = nullptr;ListNode* SmallCur = nullptr;// 定义一个指针遍历原链表ListNode* cur = pHead;if(pHead == nullptr) {return nullptr;}while(cur) {ListNode* next = cur->next; // 保存下一个节点cur->next = nullptr; // 断开当前节点的连接if(cur->val < x) {// 将节点尾插到小链表中if(SmallList == nullptr) {SmallList = cur;SmallCur = cur;} else {SmallCur->next = cur;SmallCur = SmallCur->next;}} else {// 将节点尾插到大链表中if(BigList == nullptr) {BigList = cur;BigCur = cur;} else {BigCur->next = cur;BigCur = BigCur->next;}}cur = next;}// 如果小链表为空,直接返回大链表if(SmallList == nullptr) {return BigList;}// 将大链表连接到小链表尾部SmallCur->next = BigList;return SmallList;} };
-
第二种解法:
用指针遍历原链表,小于x的不做处理,大于等于x的尾插到原链表上,并删除之前的节点
- 找到链表原始尾部
- 遍历链表,遇到≥x的节点就移动到尾部
- 如果是头节点,更新head指针
- 更新tail指针指向新的尾部
- 继续处理直到完成
/* struct ListNode {int val;struct ListNode *next;ListNode(int x) : val(x), next(NULL) {} };*/class Partition { public:ListNode* partition(ListNode* pHead, int x) {if (pHead == nullptr) return nullptr;// 找到链表尾部ListNode* tail = pHead;while (tail->next != nullptr) {tail = tail->next;}ListNode* head = pHead;ListNode* prev = nullptr;ListNode* cur = head;ListNode* end = tail; // 记录原始尾部,避免重复移动while (cur != end && cur != nullptr) {if (cur->val >= x) {// 移动到尾部if (prev == nullptr) {// 头节点需要移动head = cur->next;tail->next = cur;cur->next = nullptr;tail = cur;cur = head;} else {prev->next = cur->next;tail->next = cur;cur->next = nullptr;tail = cur;cur = prev->next;}} else {prev = cur;cur = cur->next;}}// 处理最后一个节点(原始尾部)if (cur != nullptr && cur->val >= x && cur != tail) {if (prev == nullptr) {head = cur->next;} else {prev->next = cur->next;}tail->next = cur;cur->next = nullptr;}return head;} };
关键处理点:
- 头节点移动的特殊处理:当需要移动头节点时,需要更新head指针
- prev指针的处理:prev为nullptr表示当前节点是头节点
- 尾部指针维护:每次移动节点后更新tail指针
- 循环终止条件:避免重复移动已经移动到尾部的节点
(12)链表的回文结构
-
[链表的回文结构_牛客题霸_牛客网]:
-
解题思路:
算法思路:
- 找到链表中点:使用快慢指针法,快指针每次走两步,慢指针每次走一步,当快指针到达末尾时,慢指针正好在中间位置。
- 反转后半部分链表:将链表的后半部分反转,这样就可以从两端向中间比较。
- 比较前后两部分:从链表头部和反转后的后半部分头部开始,逐个节点比较值是否相等。
- 恢复链表(可选):如果需要保持原链表结构不变,可以将反转的后半部分再次反转恢复原状。
时间复杂度: O(n) - 遍历链表3次(找中点、反转、比较)
空间复杂度: O(1) - 只使用了固定数量的指针变量/* struct ListNode {int val;struct ListNode *next;ListNode(int x) : val(x), next(NULL) {} };*/class PalindromeList { public:bool chkPalindrome(ListNode* A) {//如果原链表是空链表或者只有一个节点的链表默认就是回文结构if(A == nullptr && A->next == nullptr){return true;}//第一步:使用快慢指针找到链表的中间节点ListNode* fast = A;ListNode* slow = A;//因为fast一次走两步,所以当fast走到最后只会是指向最后一个节点(单数节点)或者指向最后一个节点的next(双数节点)//所以循环的结束条件fast != nullptr && fast->next != nullptrwhile(fast != nullptr && fast->next != nullptr){slow = slow->next;fast = fast->next->next;}//第二步:将找到的中间节点地址传给翻转链表函数(三个指针)进行翻转ListNode* newList = reverseList(slow);//第三步:通过两个链表的头指针进行遍历比较来判断是否是回文结构ListNode* Old = A;ListNode* New = newList;while(Old != nullptr && New != nullptr){if(Old->val != New->val){return false;}Old = Old->next;New = New->next;}return true;}private:struct ListNode* reverseList(struct ListNode* head) {//如果传入的链表头指针head为空,直接返回NULL,因为空链表不需要反转if(head == nullptr){return nullptr;}struct ListNode* n1,*n2,*n3;n1 = nullptr;n2 = head;n3 = head->next;while(n2){//翻转n2->next = n1;//迭代往后走n1 = n2;n2 = n3;if(n3){n3 = n3->next;}}return n1;} };