您的位置:首页 > 其它

leetcode之链表逆序翻转类-----92/206 逆序 24/25/61/143 按规则翻转 86/234 双指针分治 19/82/83/203 按规则删除

2017-10-02 13:54 507 查看
这部分考察两个部分:

1、找中点,逆序,部分逆序

2、边界细节处理

1、OJ206单链表全逆序

OJ206代码:

class Solution {
public:
ListNode* reverseBetween(ListNode* head, int m, int n) {
if (!head || !m || !n) {
return head;
}

//首先找到第m个节点cur(注意head是第一个节点, 第m个节点是head做m-1次向前)
ListNode *prev = nullptr, *cur = head;
for (int i = m - 1; i > 0; i--) {
prev = cur;
cur = cur->next;
}

//同理, 从第m个节点到第n个节点逆序, 做n-m+1次翻转. 注意一路要确保next非空
ListNode *newed = cur, *newst = cur;
ListNode *next = cur->next;
for (int i = m + 1; next && i <= n; i++) {
ListNode *nn = next->next;
next->next = newst;
newst = next;
next = nn;
}

//这里要注意m=1的情况即从head就翻转, 这样翻转后头节点要变为第n个节点
newed->next = next;
if (prev) {
prev->next = newst;
} else {
head = newst;
}

return head;
}
};

2、OJ92 单链表部分翻转
单链表第m到第n个节点逆序,其他的不变

步骤:

1、找到第m个节点,以及它前边的节点prev,注意m = 1时,第m个节点就是head,prev就是nullptr;

2、第m到第n个节点逆序,并记录下新的头节点(原链表第n个节点),第n+1个节点next;新的头指向next;

3、如果prev为空,那么返回新的头;如果不为空,prev指向新的头;

OJ92代码:

class Solution {
public:
ListNode* reverseBetween(ListNode* head, int m, int n) {
if (!head || !m || !n) {
return head;
}

//首先找到第m个节点cur(注意head是第一个节点, 第m个节点是head做m-1次向前)
ListNode *prev = nullptr, *cur = head;
for (int i = m - 1; i > 0; i--) {
prev = cur;
cur = cur->next;
}

//同理, 从第m个节点到第n个节点逆序, 做n-m+1次翻转. 注意一路要确保next非空
ListNode *newed = cur, *newst = cur;
ListNode *next = cur->next;
for (int i = m + 1; next && i <= n; i++) {
ListNode *nn = next->next;
next->next = newst;
newst = next;
next = nn;
}

//这里要注意m=1的情况即从head就翻转, 这样翻转后头节点要变为第n个节点
newed->next = next;
if (prev) {
prev->next = newst;
} else {
head = newst;
}

return head;
}
};

3、OJ24 swap Nodes in pairs:
对原链表两两翻转,不足两个的不翻转,如原先是1234,翻转后是2143,原先是12345,翻转后是21435

步骤:

1、链表节点数小于2个的不用做直接返回

2、提前记录新的头,原地两两翻转,发现后边不足两个节点时停止

OJ24代码:

class Solution {
public:
ListNode* swapPairs(ListNode* head) {
if (!head || !head->next) {
return head;
}

ListNode *cur = head, *next = cur->next, *newhead = next, *prev = nullptr;
while (cur && next) {
ListNode *nn = next->next;
next->next = cur;
cur->next = nn;
if (prev) {
prev->next = next;
}
prev = cur;
cur = nn;
if (cur) {
next = cur->next;
}
}

return newhead;
}
};

4、OJ25 reverse Nodes in K-Group
每K个节点逆序,少于k个时不翻转,如原先是12345,k=2,则变为21435,如k=3,则变为32145

OJ25其实是OJ24的变种,也需要提前记录新的头,也需要每次记录本次的新头和新尾,记录上次的尾巴和下次的头;

另外这里的方法是先遍历一次链表获取长度,提前计算出需要翻转多少次

步骤:

1、先遍历一次计算出链表长度len,k大于len的、k=1的情况,不需要翻转,尤其k=1的情况,一定提前滤掉;剩下的就是1<K<=len的情况了

2、提前计算出新的头newhead,每次翻转记录本次新的头,新的尾,下次的起点,让上次的末尾点prevk执行本次新的头,本次新的尾指向下次新的头

3、根据第1步计算出的翻转次数,做相应次数的第2步

4、返回提前计算好的新的头newhead

OJ25代码:

class Solution {
public:
ListNode* reverseKGroup(ListNode* head, int k) {
if (!head || k <= 0) {
return head;
}

//首先求链表长度len, 如果发现k大于len则直接返回原链表
int len = 0;
ListNode *st = head;
while (st) {
st = st->next;
++len;
}
if (k > len || k == 1) {
return head;
}

//如果需要翻转, 计算出需要翻转多少次并翻转, 提前记录新的头prevk, 每次记录新的头prev和尾newtail做新旧连接
st = head;
ListNode *newhead = nullptr, *prevk = nullptr;
int times = len/k;
for (int i = 0; i < times && st; i++) {
ListNode *newtail = st, *prev = nullptr;
int t = k;
while (t) {
ListNode *next = st->next;
st->next = prev;
prev = st;
st = next;
--t;
}
newtail->next = st;
if (!i) {
newhead = prev;
}
if (prevk) {
prevk->next = prev;
}
prevk = newtail;
}

return newhead;
}
};

5、OJ61 rotate list
倒数K个节点移到链表前边,如原链表是12345,给定k=2,则结果为45123

不同于数组的旋转做三次旋转的方式,链表无法直接索引,但链表有链表更高效的方式

另外,此题看似简单,但实际有个暗坑,K可能很大,远远大于len,常规方式会导致TLE,对于K很大时,这题是要求按循环链表式处理,比如k=2结果为45123,k=7、k=12、......k=2+len都是结果为45123,也就是分裂点是:idx = k % len

步骤:

1、对于空链表和只有一个元素的链表直接返回

2、遍历一次计算链表长度len,计算分裂点idx = k % len,idx如为0就无需翻转了(如k=0)

3、找到分裂点,如12345,k=2时,找到节点3,然后调整指针即可

OJ61代码:

class Solution {
public:
ListNode* rotateRight(ListNode* head, int k) {
if (!head || !head->next) {
return head;
}

//首先计算链表长度len, 这是因为k可能很大, 需要按模计算分裂点
ListNode *st = head, *ed = nullptr;
int len = 1;
while (st->next) {
st = st->next;
++len;
}
ed = st;

//计算分裂点idx = k % len, 如果发现分裂点为0说明无需翻转, 否则就按常规K翻转
//翻转方式就是找到第(len - idx)个节点, 它后面就是新的前半部分, 调整指针即可
int idx = k % len;
if (!idx) {
return head;
} else {
int st = 1;
ListNode *fast = head;
while (st < (len - idx)) {
fast = fast->next;
++st;
}
ListNode *newhead = fast->next;
fast->next = nullptr;
ed->next = head;
return newhead;
}
}
};

6、OJ143 recorder list
长度为N的链表,要原地调整为:0、N、1、N-1、2、N-2、3......这样的顺序

这题考察的是:链表中点获取、链表部分逆序,双指针操作

步骤:

1、找到链表中点

2、mid后部分逆序

3、双指针调整指针,注意奇数长度的链表,双指针会同时到达mid,偶数长度的链表,左指针到达mid时,右指针在mid右边的元素,注意调整细节

OJ143代码:

class Solution {
public:
void reorderList(ListNode* head) {
if (!head || !head->next || !head->next->next) {
return;
}

//找链表中点mid
ListNode *mid = head, *tail = head;
while (tail->next && tail->next->next) {
tail = tail->next->next;
mid = mid->next;
}

//mid后的部分逆序
ListNode *back = mid->next, *prev = mid;
while (back) {
ListNode *next = back->next;
back->next = prev;
prev = back;
back = next;
}

//双指针操作, 重新调整指针, 注意奇数个长度的链表, 双指针会相遇, 偶数个长度的链表, 左指针到达mid时, 右指针会是mid右边的元素, 注意一下调整细节
ListNode *st1 = head, *st2 = prev;
while (1) {
ListNode *st1_next = st1->next, *st2_next = st2->next;
st1->next = st2;
st2->next = st1_next;

st1 = st1_next;
st2 = st2_next;

if (st1 == mid) {
if (st1 == st2) {
st1->next = nullptr;
} else {
st1->next = st2;
st2->next = nullptr;
}
return;
}
}
}
};

7、OJ86 partition list
如原链表为35891247,给定k,如k=3,原地调整原链表为,小于3的节点在链表左部分,大于等于3的节点做链表右部分

这题需要一个技巧:虽然是原地调整,但通过创建虚拟节点方便处理过程;这是链表题的一个技巧

步骤:

1、创建虚拟节点big、small,分别用于挂接原链表的大值元素和小值元素

2、遍历原链表,大值挂在big下,小值挂在small下

3、删除big和small的虚拟头节点,并连接small和big,返回small的头节点

OJ86代码:

class Solution {
public:
ListNode* partition(ListNode* head, int x) {
//链表题的一种技巧, 虽然不让重新创建新链表, 但可以创建一个节点便于处理, 这类就是对大值和小值, 分别创建一个虚拟的头
ListNode *small = new ListNode(INT_MIN), *big = new ListNode(INT_MIN);

//大值往big下放, 小值往small下放
ListNode *st1 = small, *st2 = big;
ListNode *cur = head;
while (cur) {
ListNode *next = cur->next;
int v = cur->val;
if (v >= x) {
big->next = cur;
big = cur;
big->next = nullptr;
} else {
small->next = cur;
small = cur;
small->next = nullptr;
}
cur = next;
}

//删除虚拟节点, 连接大值链表和小值链表
small->next = st2->next;
ListNode *newst = st1->next;
delete st1;
delete st2;
return newst;
}
};

8、OJ234 partition linked list
判断链表是否是一个回文

不同于数组的判断回文,链表无法直接索引,链表有链表的方式,本题和OJ143如出一辙,同样考察的是:链表中点、局部逆序、双指针操作

步骤:

1、对于链表节点数为0、1、2的链表自行处理;

2、找中点mid,mid后的部分逆序

3、双指针操作判断是否是回文,同样注意链表长度为奇数、偶数时的细节差别;

4、本题比较恶心的是,判断后还需要还原,即原先逆序的后半部分还要恢复回来

OJ234代码:

class Solution {
public:
bool isPalindrome(ListNode* head) {
if (!head) {
return true;
}

//找中点mid
ListNode *mid = head, *fast = head;
while (fast->next && fast->next->next) {
mid = mid->next;
fast = fast->next->next;
}

//提前处理掉只有1-2个节点的情况
if (mid == head) {
if (mid->next) {
return (mid->val == mid->next->val)?true:false;
} else {
return true;
}
}

//mid后的部分逆序
ListNode *cur = mid->next, *prev = mid, *next = cur->next;
if (!next) {
return (head->val == cur->val)?true:false;
} else {
while (next) {
ListNode *nn = next->next;
next->next = cur;
cur->next = prev;
prev = cur;
cur = next;
next = nn;
}
}

//双指针判断回文, 奇数个长度时会相遇在mid,偶数个长度时, 左指针到达mid时, 右指针会在mid后的元素, 注意一下调整
bool res = false;
ListNode *left = head, *right = cur;
while (left != mid) {
if (left->val == right->val) {
left = left->next;
right = right->next;
} else {
res = false;
break;
}
}

if (left == right) {
res = true;
} else {
res = (left->val == right->val)?true:false;
}

//本题比较恶心的是还需要还原原链表
ListNode *p = cur, *q = nullptr, *n = p->next;
while (n != mid) {
ListNode *nn = n->next;
n->next = p;
p->next = q;
q = p;
p = n;
n = nn;
}
return res;
}
};

9、OJ19 remove Nth Node From End Of List
删除链表倒数第N个节点

本题看似简单但AC不易,需要考虑多种情况:

1、链表根本不足N个节点

2、整好是倒数第1个节点

所以本题考察的是边界细节处理能力;

步骤:

1、快指针先行N步,如果途中未到N步fast已到头,说明链表不足N个节点无需处理直接返回,如果fast到头且N==0,说明链表整好N个节点

2、如果链表整好N个节点,删除头节点,注意返回第二个节点为新的头

3、如果链表大于N个节点,快慢指针继续同行直到快指针为空,则慢指针到达倒数第N个节点前面的节点,进行链表节点删除

OJ19代码:

class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
if (!head || n <= 0) {
return head;
}

//快指针先行N步, 如果链表不足N个节点, fast为nullptr, 如果链表整好N个节点, n==0且fast为nullptr
ListNode *slow = head, *fast = head;
while (n && fast) {
fast = fast->next;
--n;
}

//区分链表大于N个节点和整好N个节点的情况, 前者正常进行, 后者需要删除掉头节点
if (fast) {
while (fast->next) {
slow = slow->next;
fast = fast->next;
}
ListNode *delnode = slow->next;
ListNode *newnode = delnode->next;
slow->next = newnode;
delete delnode;
return head;
} else if (!n) {
ListNode *next = head->next;
delete head;
return next;
}
}
};

10、OJ83 remove duplicates from sorted list
有序单链表原地去重,如原先为12334455,原地去重后为12345

考察双指针操作和细心

OJ83代码:

class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
if (!head) {
return head;
}

ListNode *cur = head, *next = head->next;
int curval = head->val;
while (next) {
if (next->val != curval) {
curval = next->val;
cur = next;
next = cur->next;
} else {
ListNode *nn = next->next;
delete next;
cur->next = nn;
next = nn;
}
}

return head;
}
};

11、OJ203 remove linked list elements
删除链表中节点value为给定值的节点

看似简单其实AC十分不易,对于本题没有什么好说的,一定想清楚如下的情况:

1、如果一开头就是要删除的节点,怎么办

2、遇到要删除的节点,怎么办

3、一定处理好往下遍历过程中的next,多思考可能为nullptr的情况

一定要想清楚足够的case

OJ203代码:

class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
if (!head) {
return head;
}

//首先处理掉头节点就为删除点的情况
ListNode *cur = head, *next = cur->next;
while (cur && cur->val == val) {
delete cur;
cur = head = next;
if (cur) {
next = cur->next;
} else {
return head;
}
}

//全是细节处理
while (next) {
if (next->val == val) {
while (next && next->val == val) {
ListNode *nn = next->next;
delete next;
next = nn;
}
cur->next = next;
cur = cur->next;
if (cur) {
next = cur->next;
} else {
break;
}
} else {
cur = next;
next = cur->next;
}
}

return head;
}
};

12、OJ82 remove duplicates from sorted list II

一点资讯面试原题本人曾踩坑,本题如果按常规做法搞,估计会很被动。。。

如果用循环做,可能相当麻烦,而且极易出错;后来发现这道题适合递归,即把每个找重复的过程用递归连起来,而不是在循环里相互联系;

步骤:

1、当发现节点为nullptr,或者没有next了,则返回该节点

2、取得next;

2.1、如果发现next和当前节点value重复,立即遍历链表直到不重复为止;这样所有值重复节点就会被略过了,然后以这个不重复的节点,递归调用1;

  注意,值重复的节点未做delete释放也是可以AC;

2.2、如果发现next不和当前节点value重复,就用该节点继续递归调用1;

第2步的核心目的是:找到当前节点应该的下一个节点,而递归的方式,巧妙避开了prev、cur这种容易混乱的判断和更新;

OJ82代码:

class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
//空的或只有它自己, 返回
if (!head) {
return nullptr;
} else if (!head->next) {
return head;
}

//取得next, 如果发现重复, 严查全部的重复节点直到不重复
ListNode *next = head->next;

if (next->val == head->val) {
int dup = head->val;
while (next && dup == next->val) {
next = next->next;
}
return deleteDuplicates(next);
} else {
head->next = deleteDuplicates(next);
return head;
}
}
};
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐