解题思路
链表反转这种题,上个星期我还是不会做的,但是自从学会了前插法,写起来就游刃有余的。(说明刷题还是有进步的)
下面介绍下前插法
- 新建一个空节点作为反转链表的表头
- 将待反转链表的头结点取出,用一个指针指向头结点的下一个结点,然后将头结点指向新链表的表头
- 新链表的指针指向插入的头结点,待反转链表的表头为之前存放的旧结点的next结点
- 重复上述操作,直到反转链表全部插入到新链表中
- 返回新链表的头结点即可
这样看着不好理解,直接看代码吧
24. 两两交换链表中的节点
思路
三个指针来交换
口 1 2 3 4
s p q
口 2 1 3 4
s q p
口 2 1 3 4
s q p
如上 p,q指向需要交换的指针,s为p指针之前的指针(没有s会导致漏掉数)
1.p先指向q next
2.q的next为p
3.s的next为q
4.判断如果p余下的空间不够交换 就可以返回ans了
5.如果够 那么继续往下迭代
注意一开始要用ans来指向头指针 不然head和start是会改变位置的,直接返回会漏掉数
代码
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
if(!head||!head->next) return head;
ListNode* ptr=head;
ListNode* qtr=head->next;
ListNode* start=new ListNode(0);
start->next=head;
ListNode* ans=start;
while(start->next)
{
// cout<<"start "<<start->val<<"ptr "<<ptr->val<<"qtr "<<qtr->val<<endl;
ptr->next=qtr->next;
qtr->next=start->next;
start->next=qtr;
if(ptr->next==NULL||ptr->next->next==NULL)return ans->next;
start=ptr;
ptr=start->next;
qtr=ptr->next;
}
return ans->next;
}
};
25.k个一组反转链表
这道题是我第一次独立做的困难题(虽然是通过率很高的困难题) 心里还是挺有成就感的,下面说一下我的思路
思路
首先我想到这题跟反转链表其实很像,就是用前插法翻转链表,只不过用的指针有点多
- need指向需要划分的区间的起始结点,我用一个ptr来指向need,方便划分
- 然后用need往下走,走到下一个区间的起始结点
- 现在我们有了翻转区间的起始结点和结束条件,那么我们就可以开始用前插法来翻转区间了
- ok,翻转完区间,我们把翻转完区间的尾结点(其实就是更改前的need哦,不用多次遍历!),指向下一个需要翻转的尾结点。这样做能使得你的链表每一次翻转都能连贯起来,并且下一次翻转不会断链,不信可以用笔验算一下。(如果碰到剩下的不用翻转怎么办?直接指向当前的need结点返回即可!)
- 然后我们返回链表需要注意一下,返回的是第一个区间的尾结点为头结点的链表,所以第一步要往后找到这个头结点即可。
简单的说就三步
1.划分反转区间,反转该区间
2.该区间指向下一个反转区间的尾结点(这样可以连贯的反转)
3.重复上述步骤,直到判断出剩余的结点不足够反转,就可以退出了
下面是代码,有一点长,用时16ms
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* reverseKGroup(ListNode* head, int k) {
if(head==NULL||head->next==NULL)return head;
//题目的k有可能大于链表中的个数
ListNode* thead=head;
int num=0;
while(thead)
{
num++;
thead=thead->next;
}
if(k>num) return head;
//ans为需要返回的新链表的头结点
ListNode* ans=head;
//need为划分区间
ListNode* need=head;
for(int i=0;i<k-1;i++)
{
if(ans) ans=ans->next;
else return head;
}
bool enough=true;
ListNode* ptr=need;
ListNode* tneed=need;
//insTo为翻转区域的尾节点
ListNode* insTo;
//ins为需要指向的下一个区间的结点
ListNode* ins=need;
while(need&&enough)
{
//指向尾结点
insTo=need;
//用于遍历的临时结点
tneed=need;
//翻转区间的起点
ptr=need;
//不应该在这里判断
//用一个临时结点来判断!这一轮!是否足够翻转
for(int i=0;i<k;i++)
{
if(need) need=need->next;
}
//前插法反转该链表 无论如何都需要翻转前一区间的链表
ListNode* temp=new ListNode(0);
while(ptr!=need)
{
ListNode* qtr=ptr;
ptr=ptr->next;
qtr->next=temp;
temp=qtr;
}
//这里还需另外判断下一轮够不够!!
tneed=need;
for(int i=0;i<k;i++)
{
if(tneed) tneed=tneed->next;
else enough=false;
}
//1. 下一轮没有足够的结点用于反转 直接指向need
if(enough==false)
{
insTo->next=need;
}
else
{
//非空结点需要指向need的下k-1个结点
ins=need;
for(int i=1;i<k;i++)
{
if(ins)ins=ins->next;
}
insTo->next=ins;
}
}
return ans;
}
};
别人的思路
看了下别人的代码。是每次都交换两个结点,而不是一次性交换所有结点,大同小异吧,只不过我用的空间更多些
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *reverseKGroup(ListNode *head, int k) {
if (!head || k == 1) return head;
ListNode *dummy = new ListNode(-1);
ListNode *pre = dummy, *cur = head;
dummy->next = head;
int i = 0;
while (cur) {
++i;
if (i % k == 0) {
pre = reverseOneGroup(pre, cur->next);
cur = pre->next;
} else {
cur = cur->next;
}
}
return dummy->next;
}
ListNode *reverseOneGroup(ListNode *pre, ListNode *next) {
ListNode *last = pre->next;
ListNode *cur = last->next;
while(cur != next) {
last->next = cur->next;
cur->next = pre->next;
pre->next = cur;
cur = last->next;
}
return last;
}
};