翻转链表

力扣题目链接(opens new window)

题意:反转一个单链表。

示例: 输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL

思路分析

双指针法是本体的最基本的解法,由此还可以改写为递归解法

双指针法

我们需要定义两个指针,pre和cur

初始时,cur指向头节点pre指向null(pre需要在cur之前,前面又没有节点,那只能是null咯。或者可以这么理解:翻转之后此时pre的位置会变成链表尾部,那链表尾部肯定指向null,所以需要将pre初始化为null)

让cur指向前一个节点

此时为了保存cur的下一个节点,好翻转之后让指针顺利移动到下一位置,我们需要先有一个临时节点temp

然后先把pre移动到当前cur的位置

再根据temp将cur移动到正确的下移位置

注意:这里先移动cur再移动pre的话会导致pre移动到的并不是之前cur的位置,因为cur的值已经先于pre改变(刻舟求剑懂不懂?)

之后就重复上述步骤即可

那么什么时候结束循环呢?当cur指到原链表的尾部(null)时便可结束,此时pre指向的节点为新的头节点

代码

明确步骤之后代码就很好写了

  1. class Solution {
  2. public ListNode reverseList(ListNode head) {
  3. //双指针法
  4. //初始化两个指针
  5. ListNode cur = head;
  6. ListNode pre = null;
  7. //定义用于存放cur下一节点的临时节点temp
  8. ListNode temp;
  9. while(cur != null){//从旧的头节点开始遍历,到翻转前的链表尾部(也就是null)结束
  10. temp = cur.next;
  11. cur.next = pre;
  12. //注意,cur翻转完之后一定先让pre按照当前cur的值移动到指定位置,再让cur更新为temp
  13. //不然cur先动了pre就找不到之前cur的位置了
  14. pre = cur;
  15. cur = temp;
  16. }
  17. return pre;//翻转结束,此时pre指向的就是新的头节点
  18. }
  19. }
c++版

思路一致,代码对应着写就可以

  1. class Solution {
  2. public:
  3. ListNode* reverseList(ListNode* head) {
  4. //双指针法
  5. ListNode* pre = nullptr;
  6. ListNode* cur = head;
  7. while(cur != nullptr){
  8. //保存cur的下一节点
  9. ListNode* temp = cur->next;
  10. //交换
  11. cur->next = pre;
  12. //一定要先移动pre
  13. pre = cur;
  14. cur = temp;
  15. }
  16. return pre;//此时的pre为新链表的head
  17. }
  18. };
递归法

递归法主要是针对双指针法在代码层面上的一个优化,原理层面与双指针法一致

因此递归法的代码可以与双指针法的一一对应

代码

LeetCode上解题模板中,Solution类给了一个主方法reverseList

那么根据递归的写法,我们还要写一个reverse方法,让reverseList去调用它来翻转链表

  1. class Solution {
  2. public ListNode reverse(ListNode pre, ListNode cur){
  3. }
  4. public ListNode reverseList(ListNode head) {
  5. reverse();
  6. }
  7. }

reverseList传入的参数是head,那么reverse需要的两个参数怎么传?

参考双指针法的初始化部分,reverse中的两个参数也要对应进行初始化

即:

  1. class Solution {
  2. public ListNode reverse(ListNode pre, ListNode cur){
  3. }
  4. public ListNode reverseList(ListNode head) {
  5. reverse(null head);//与双指针法对应
  6. }
  7. }

接下来按照双指针思路把reverse的功能完善即可,完整代码如下:

  1. class Solution {
  2. public ListNode reverse(ListNode cur, ListNode pre){
  3. ListNode temp;
  4. if(cur == null){
  5. return pre;
  6. }
  7. temp = cur.next;//保存cur.next
  8. cur.next = pre;//翻转操作
  9. //接下来要让pre和cur整体向后移动一位,在递归里应该直接用return来实现,下面代码对应于
  10. //pre = cur;
  11. //cur = temp;
  12. return reverse(temp, cur);//进入下一层递归
  13. }
  14. public ListNode reverseList(ListNode head) {
  15. return reverse(head, null);//与双指针法对应
  16. }
  17. }
c++版

c++的解题模板如下:

  1. class Solution {
  2. public:
  3. ListNode* reverseList(ListNode* head) {
  4. }
  5. };

要是用递归的话,要再写一个成员函数reverse,该函数的返回类型仍然是ListNode*

  1. class Solution {
  2. public:
  3. ListNode* reverse(ListNode* pre, ListNode* cur) {
  4. }
  5. ListNode* reverseList(ListNode* head) {
  6. }
  7. };

代码如下

  1. class Solution {
  2. public:
  3. ListNode* reverse(ListNode* cur, ListNode* pre) {
  4. //这里就不好用while了因为要递归
  5. if(cur == nullptr){
  6. return pre;
  7. }
  8. ListNode* temp = cur->next;
  9. cur->next = pre;
  10. //接下来要用return实现以下代码
  11. // pre = cur;
  12. // cur = temp;
  13. //从而达到递归
  14. return reverse(temp, cur);//对着要实现的代码来填就行了
  15. }
  16. ListNode* reverseList(ListNode* head) {
  17. //这里要实现双指针法中的
  18. // ListNode* pre = nullptr;
  19. // ListNode* cur = head;
  20. //部分代码
  21. return reverse(head,nullptr);//对应这填即可
  22. }
  23. };

二刷易错点

1、节点翻转环节的顺序

当使用临时节点保存cur的下一节点并将cur指向pre后,应该先将pre移动到cur处,再移动cur

要不然会乱

2、递归实现

递归版本完全可以根据双指针版本写出来,因此记住双指针版本的实现原理即可

需要注意,在递归实现中,不要使用while

K个一组翻转链表

https://leetcode.cn/problems/reverse-nodes-in-k-group/description/

给你链表的头节点 head ,每 k 个节点一组进行翻转,请你返回修改后的链表。

k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。

你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。

思路

这里需要控制翻转区间,也就是每k个范围翻转一次

如果使用之前的迭代的方式,区间控制逻辑会变得很复杂,很容易出错(双层for加一堆变量),所以这里换用递归的方式来做

具体就是我们先定义一个指针post,指向head,然后遍历k步,此时就有了区间[head, post],该区间长度为k

然后我们还是定义pre和cur,将区间内的节点全部翻转

翻转结束后,cur、post以及翻转前的head节点会重叠到一起,此时我们需要继续翻转下一个k区间,也就是说我们要找head->next

因此,调用递归函数继续重复之前的操作:移动post确定区间,使用pre和cur翻转区间

最后,post为空,返回此时的pre即可。

代码

写法上遵守递归的三步走

  1. class Solution {
  2. public:
  3. ListNode* reverseKGroup(ListNode* head, int k) {
  4. ListNode* post = head; //用于标定翻转区间
  5. for(int i = 0; i < k; ++i){
  6. if(post == nullptr) return head;//终止条件
  7. post = post->next;
  8. }
  9. ListNode* pre = nullptr;
  10. ListNode* cur = head;
  11. while(cur != post){//翻转区间内的节点
  12. ListNode* tmp = cur->next;
  13. cur->next = pre;
  14. pre = cur;
  15. cur = tmp;
  16. }
  17. //区间翻转完成,此时的head又被移动(交换)到了post处
  18. //之后就调用递归翻转下一个k区间
  19. //head不能动,所以输入递归函数的应该是cur
  20. head->next = reverseKGroup(cur, k);
  21. return pre;//与常规翻转一样,最后pre会指向链表头部,cur会指向空
  22. }
  23. };

ACM模式

  1. #include <iostream>
  2. using namespace std;
  3. struct ListNode {
  4. int val;
  5. ListNode* next;
  6. ListNode(int x) : val(x), next(NULL) {}
  7. };
  8. class Solution {
  9. public:
  10. ListNode* reverseKGroup(ListNode* head, int k) {
  11. ListNode* post = head; //用于标定翻转区间
  12. for (int i = 0; i < k; ++i) {
  13. if (post == nullptr) return head;//终止条件
  14. post = post->next;
  15. }
  16. ListNode* pre = nullptr;
  17. ListNode* cur = head;
  18. while (cur != post) {//翻转区间内的节点
  19. ListNode* tmp = cur->next;
  20. cur->next = pre;
  21. pre = cur;
  22. cur = tmp;
  23. }
  24. //区间翻转完成,此时的head又被移动(交换)到了post处
  25. //之后就调用递归翻转下一个k区间
  26. //head不能动,所以输入递归函数的应该是cur
  27. head->next = reverseKGroup(cur, k);
  28. return pre;//与常规翻转一样,最后pre会指向链表头部,cur会指向空
  29. }
  30. };
  31. int main() {
  32. ListNode* head = new ListNode(1);
  33. head->next = new ListNode(2);
  34. head->next->next = new ListNode(3);
  35. head->next->next->next = new ListNode(4);
  36. head->next->next->next->next = new ListNode(5);
  37. int k = 2;
  38. Solution solution;
  39. ListNode* res = solution.reverseKGroup(head, k);
  40. while (res) {
  41. cout << res->val << ' ';
  42. res = res->next;
  43. }
  44. return 0;
  45. }

【LeetCode链表#8】翻转链表(双指针+递归)/K个一组翻转的更多相关文章

  1. leetcode 24. 两两交换链表中的节点 及 25. K 个一组翻转链表

    24. 两两交换链表中的节点 问题描述 给定一个链表,两两交换其中相邻的节点,并返回交换后的链表. 你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换. 示例: 给定 1->2-> ...

  2. [LeetCode] 25. Reverse Nodes in k-Group 每k个一组翻转链表

    Given a linked list, reverse the nodes of a linked list k at a time and return its modified list. k  ...

  3. [LeetCode]25. Reverse Nodes in k-Group k个一组翻转链表

    Given a linked list, reverse the nodes of a linked list k at a time and return its modified list. k ...

  4. Java实现 LeetCode 25 K个一组翻转链表

    25. K 个一组翻转链表 给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表. k 是一个正整数,它的值小于或等于链表的长度. 如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持 ...

  5. LeetCode 25 —— K 个一组翻转链表

    1. 题目 2. 解答 首先,利用快慢指针确定链表的总结点数. 偶数个结点时,结点个数等于 i * 2. 奇数个结点时,结点个数等于 i * 2 + 1. 然后将链表的每 K 个结点划分为一组.循环对 ...

  6. leetcode 25. K 个一组翻转链表

    # coding:utf-8 __author__ = "sn" """ 25. K 个一组翻转链表 给你一个链表,每 k 个节点一组进行翻转,请你返 ...

  7. LeetCode 25. K 个一组翻转链表 | Python

    25. K 个一组翻转链表 题目来源:https://leetcode-cn.com/problems/reverse-nodes-in-k-group 题目 给你一个链表,每 k 个节点一组进行翻转 ...

  8. [LintCode] Reverse Nodes in k-Group 每k个一组翻转链表

    Given a linked list, reverse the nodes of a linked list k at a time and return its modified list. If ...

  9. k个一组翻转链表(java实现)

    题目: 给出一个链表,每 k 个节点一组进行翻转,并返回翻转后的链表. k 是一个正整数,它的值小于或等于链表的长度.如果节点总数不是 k 的整数倍,那么将最后剩余节点保持原有顺序. 示例 : 给定这 ...

  10. LeetCoded第25题题解--K个一组翻转链表--java--链表

    链表 单链表:链表中的每个元素实际上是一个单独的对象,而所有对象都通过每个元素的引用字段链接在一起. 双链表:与单链表不同的是,双链表的每个节点都含有两个引用字段. 链表优点 灵活分配内存空间 能在O ...

随机推荐

  1. gpedit.msc 打不开

    win10系统推出已有不短的时间了,朋友们也纷纷升级了win10系统,但是暴露的问题也是越来越多,比如win10系统打开运行输入gpedit.msc命令时却提示找不到文件.那出现win10打不开gpe ...

  2. tortoisegit 还原远程分支到某个版本

    v2还原到v1 1.强制还原(git reset) 如果使用这种方式还原到v1,将丢失还原到v1到v2之间的所有提交及日志. 1.1显示日志 有save1.save2两条提交记录. 1.2 重置版本( ...

  3. 17.3 实现无管道反向CMD

    WSASocket无管道反向CMD,与无管道正向CMD相反,这种方式是在远程主机上创建一个TCP套接字,并绑定到一个本地地址和端口上.然后在本地主机上,使用WSASocket函数连接到远程主机的套接字 ...

  4. 8.3 NtGlobalFlag

    NtGlobalFlag 是一个Windows内核全局标记,在Windows调试方案中经常用到.这个标记定义了一组系统的调试参数,包括启用或禁用调试技术的开关.造成崩溃的错误代码和处理方式等等.通过改 ...

  5. 7.4 C/C++ 实现链表栈

    相对于顺序栈,链表栈的内存使用更加灵活,因为链表栈的内存空间是通过动态分配获得的,它不需要在创建时确定其大小,而是根据需要逐个分配节点.当需要压入一个新的元素时,只需要分配一个新的节点,并将其插入到链 ...

  6. HTTP请求头引发的注入问题 (SQL注入)

    关于请求头中注入问题的演示,这里我写了一些测试案例,用来测试请求头中存在的问题.我们常见的会发生注入的点有 Referer.X-Forwarded-For.Cookie.X-Real-IP.Accep ...

  7. Python-集合的基本操作(set)

    1. 前言 python中的集合和数学里的类似也是用于存放不重复的元素,它有可变集合(set)和不可变集合(feozenset)两种,集合的所有元素都放在一对大括号"{}"里(列表 ...

  8. Google全球分布式数据库:Spanner

    2012年的OSDI上google发布了Spanner数据库.个人认为Spanner对于版本控制,事务外部一致性的处理,使用TrueTime + Timestamp进行全球备份同步的实现都比较值得一看 ...

  9. ABC 310

    E \(dp[i][j]\) 表示前 \(i\) 个里有多少个后缀答案为 \(j\). \(if (c[i] == '0') \{\) \(dp[i][0] = 1;\) \(dp[i][1] = d ...

  10. Executors.newFixedThreadPool(int nThreads)存在的缺陷

    一般来讲是不推荐直接使用JAVA提供的Executors类来初始化线程池,如果有需要可以自行通过ThreadPoolExecutor来封装进行初始化. 可以用newFixedThreadPool(in ...