leetcode之链表逆序翻转类-----92/206 逆序 24/25/61/143 按规则翻转 86/234 双指针分治 19/82/83/203 按规则删除
来源:互联网 发布:python可以建网站吗 编辑:程序博客网 时间:2024/05/05 10:18
这部分考察两个部分:
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; } }};
- leetcode之链表逆序翻转类-----92/206 逆序 24/25/61/143 按规则翻转 86/234 双指针分治 19/82/83/203 按规则删除
- 链表翻转/逆序
- 单链表逆序,直接翻转指针!!
- 链表逆序和链表翻转
- IOS nsarray 逆序翻转
- 字符串逆序翻转
- 三次翻转解决串逆序
- 链表基础之翻转,删除
- 华为OJ:字符逆序和单词翻转
- 实现字符串翻转(逆序输出)
- 数据结构之链表(头指针链表的插入、删除、逆序)
- 数据结构之头指针链表的逆序、输出和指定位置的删除
- 指针实现链表逆序
- 从尾到头打印链表(五种方法包括链表翻转、递归、栈、容器逆序)
- 华为OJ训练之0018-170105-字符串翻转(字符逆序题目)
- 字节按位逆序以及单向链表逆序
- 从链表翻转看指针引用
- leetCode之翻转二叉树
- Java生产者与消费者模式的简单写法
- c的部分算术运算符和算术表达式:
- DES算法的实现
- 【深入PHP 面向对象】读书笔记(八)
- 数据库的锁机制
- leetcode之链表逆序翻转类-----92/206 逆序 24/25/61/143 按规则翻转 86/234 双指针分治 19/82/83/203 按规则删除
- POJ 2965--The Pilots Brothers' refrigerator
- 《java基础与案例开发详解》(四)
- Mac下Nginx安装环境配置详解
- memcached的常用命令
- git使用流程及常用命令
- Java300StudyNote(8)-快速理解JDK&JRE&JVM
- Node.js学习-环境安装与配置
- 数字货币开发的底层技术如何实现执行智能合约代码