题目链接
题意:
重构一个单链表,O(1)空间复杂度,O(n)的时间复杂度条件下将链表L1~L2~L3~……~Ln-1~Ln重构为L1~Ln~L2~Ln-1~L3……
思路:
看似是一个双指针的题,一头一尾两个指针向中间合并。然而题目中的单链表这一数据结构使得尾指针无法向前移动,题目难度瞬间增大。
于是,只能利用链表操作来完成。思路也比较清晰,关键就是链表操作细节点很多。化整为零,可以通过三个小操作来实现这个任务:
1.将单链表一分为二
2.将后半部分链表翻转
3.再将两段链表合并
下面详细来说明这三部分
1.这部分主要就是统计一下链表长度,然后用两个指针分别指向链表头和链表中间的位置。对于链表长度为奇数的情况,则将多余的一个节点放在前段。
这里,出现了这个问题最容易出错的地方,那就是当找到第二段节点位置的时候,一定不要忘记将两段链表断开。如果这里忘记断开,后面第三部分链表合并的步骤会出现死循环。
而断链的过程需要链表前段的尾指针,所以还需再遍历链表前段一次。
2.链表翻转,这是一个经典问题,为了方便起见,我单独写了一个函数。
看似一个很简单的操作,然而,实际操作却并非很轻松。
不难发现翻转链表只需要把每个节点的指针指向前一个节点,主要分为两个步骤,断掉后面的链和将链连到前方,虽然这两个操作看似可以一步完成,但却一次性涉及了三个节点,需要用三个指针分别存储这三个节点。每次操作完成后分别将三个节点后移。
这里,又出现了一个很容易出错的地方,那就是链表操作前一定要判空
3.最后一部分就是将两个链表合并,那么双指针分别指向两个链表,轮流将每个指针节点的链指向另一个链表,然后指针后移即可。然而由于单链表的特性,断链后无法找到后半部分的位置,于是还需要额外两个指针来记录每个指针的下一个节点。
代码:
/*
Author Owen_Q
*/
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* reverseLink(ListNode *root)
{
//cout << root->val << endl;
if(root == NULL)
return NULL;
//cout << root->val << endl;
ListNode *a = NULL;
ListNode *b = root;
ListNode *c = root->next;
//cout << a->val << "&" << b->val << "&" << c->val << endl;
while(b!=NULL)
{
b->next = a;
a = b;
b = c;
if(c!=NULL)
c = c->next;
/*if(a!=NULL)
cout << a->val << "&";
if(b!=NULL)
cout << b->val << "&" ;
if(c!=NULL)
cout << c->val << "&";
cout << endl;*/
}
return a;
}
void reorderList(ListNode* head) {
int len = 0;
ListNode *t = head;
while(t!=NULL)
{
len++;
t = t->next;
}
if(len>1)
{
if(len%2==0)
len /= 2;
else
len = len/2+1;
int ll = len-1;
ListNode *m = head;
ListNode *n = head;
//cout << m-> val << endl;
while(len--)
{
/*if(len == 1)
{
ListNode *tt = n->next;
n->next = NULL;
n = tt;
}
else*/
n = n->next;
}
//cout << n->val << endl;
n = reverseLink(n);
cout << n->val << endl;
t = head;
while(ll--)
{
t = t->next;
}
t->next= NULL;
//cout << n->val << endl;
while(n!=NULL)
{
//cout << "*";
ListNode *mm = m->next;
ListNode *nn = n->next;
//cout << m->val << "*" << n->val << " " << mm->val << "*" << nn->val ;
m->next = n;
n->next = mm;
m = mm;
n = nn;
//cout << m->val << "*" << n->val << " ";
}
}
}
};
思路(递归版):
此外,再提供一种递归版思路,相对而言实现难度较为简单,也没有很多的坑。
其实这道题很容易就可以想到递归分治来解决。
因为,重构一个L1~……~Ln的链表,完全可以将收尾结点提出,递归为:L1~Ln~(重构一个L2~……~Ln-1的链表)。
只要确保每次递归复杂度为O(1),即可达到最终O(n)的时间复杂度。
不难发现,对于单链表,最消耗时间的即为尾结点的寻找,如果能优化掉这部分,问题将顺利解决。
于是,问题简化为将head~A……B~tail[~nextnode]转化为head~tail~A……B[~nextnode],这里的A……B即为递归简化的子问题,这里假设A非空,但可能为单结点。如果每次递归能将B结点返回,那么尾结点(tail)即为B结点的下一个结点,完美解决。
最后考虑一下递归边界的问题,由于存在边界问题,因此需要计算一下当前递归所剩长度,奇数长度递归边界为单节点,偶数长度递归边界为双结点(显然,此种情况下,尾结点为双结点的后一个结点)。
代码:
class Solution {
public:
ListNode* recursiveLink(ListNode *head,int len)
{
if(len==1)//single link
return head;
else if(len==2)//double link
return head->next;
else
{
ListNode* B = recursiveLink(head->next,len-2);
ListNode* A = head->next;
ListNode* tail = B->next;
ListNode* nextnode = tail->next;
head->next = tail;
tail->next = A;
B->next = nextnode;
return B;
}
}
void reorderList(ListNode* head) {
ListNode *tmp = head;
int len = 0;
while(tmp!=NULL)
{
len++;
tmp = tmp->next;
}
if(len>0)
recursiveLink(head,len);
}
};