LeetCode刷题日记之K个一组翻转链表

今天刷到LeetCode第25题,记录一下刷题的思路,方便以后回看。(真的一周不写就容易忘啊,所以还是要多练)

这个题大概有三种解法:

  1. 借助栈先进后出的思路,当链表元素k个一组放进栈中,然后在拿出来。(缺点是时间复杂度较高,入栈出栈都要遍历链表,不推荐,了解思路即可)。
  2. 递归:k个一组进行递归,具体思路请参考后面图解。
  3. 迭代:同上,参考后面图解。

话不多说,先上代码:

借助栈

        Deque<ListNode> stack = new LinkedList<>();

        ListNode dummy = new ListNode(0);
        //这个p会一直移动,需要另起一个指针
        ListNode p = dummy;
        while (true) {
            int count = 0;
            //这儿不能直接head移动,因为如果不足k个,head指针将找不到,因此另起一个指针
            ListNode temp = head;
            //k个一组放进栈中
            while (temp != null && count < k) {
                stack.push(temp);
                temp = temp.next;
                count++;
            }
            //当不足k个不用反转
            if (count != k) {
                p.next = head;
                break;
            }
            while (!stack.isEmpty()) {
                p.next = stack.pop();
                p = p.next;
            }
            //将head替换为k个位置之后,方便下次循环
            head = temp;
        }

        return dummy.next;

这个思路其实没啥好说的,用一个指针p依次的跟着栈往后移,当不足k个时直接返回头指针dummy.next。

看链表代码的几个重点思路

这里需要强调的几点是在链表写代码时候的常识吧:

1、链表里面经常会有如下代码,初学者看着会很奇怪,我一开始学习的时候也疑惑了很久:

        ListNode dummy = new ListNode(0);
        ListNode p = dummy;

这里已经新创建了一个dummy,为啥还要再创建一个和dummy一样的p?为啥不直接用dummy?

原因是,当你新建的dummy节点在后续操作的时候,会进行移动,如p=p.next这种,如果直接用dummy去操作,后面你拿到的dummy就不是原来的位置了,而是移动后的位置,这时候如果你想要拿到原来的dummy,就需要新创建一个帮手去帮dummy做这个事,也就是p。

上面这个逻辑在链表代码里面会经常用到,搞懂其原因后在看代码就不会有种云里雾里的感觉了。

2、链表里面经常会出现下面这种xx.next满天飞的情况,理解起来会很困难。

head.next=next.next.next;
next.next=head;

你按照我的思路来想其实就会简单很多,当xx.next在表达式左边的时候,就只有一个含义,xx的下一个节点指向表达式右边的位置

head.next=next.next;就是指head指向next的下一个节点。你这样去梳理下以前看着比较懵逼的代码就会清晰很多。反正我思路是清晰很多。

3、一旦A.next=B之后,代表A以前指向的位置指针已经断开,现在指向B了。如果后续还需要用以前A.next的节点话就需要提前记录下来。因此就会在链表了经常出现下面这种代码:

                ListNode next = tail.next.next;
                tail.next.next = prev.next;
                prev.next = tail.next;
                tail.next = next;

因为tail.next.next在第二行断开了,第四行有需要用到,所以会提前用Next把tail.next.next存起来。

第三点乍看和第一点很像,但是还是有细微区别的,一个是指针移动的记录,一个是指针断开的记录。

递归

        int count = 0;
        ListNode curr = head;
        while (curr != null && count < k) {
            curr = curr.next;
            count++;
        }

        if (count == k) {
            curr = reverseKGroup(curr, k);
            while (count-- > 0) {
                ListNode temp = head.next;
                head.next = curr;
                curr = head;
                head = temp;
            }
            head = curr;
        }

        return head;

代码其实还是很清晰的,我们一点一点来理一下。

        int count = 0;
        ListNode curr = head;
        while (curr != null && count < k) {
            curr = curr.next;
            count++;
        }

上面这段代码的主要目的就是k个节点分组,这里ListNode curr=head;要用我上面的基本点想一下,由于如果直接用head后移的话初始的head位置就找不到了,因此需要一个帮手curr去帮它做这个事。

接着往下看,如果有k个节点,就k个一组翻转,否则直接返回。

curr = reverseKGroup(curr, k); 这个是递归调用,这里需要说明的一点是在看递归相关代码不要强行去人肉递归,你会很痛苦的,且会把自己搞晕,毕竟我们不是机器。我们只需要知道这个代码后面得出的结果就是经过reverseKGroup(curr, k)这个函数处理的curr就直接是k个一组翻转好的结果,我们现在唯一要做的就是,将curr前面的k个元素翻转好在组装就行。

而下面的代码就是做的这个事:

            while (count-- > 0) {
                ListNode temp = head.next;
                head.next = curr;
                curr = head;
                head = temp;
            }
            head = curr;

这块不是那么好理解,碰到这种情况一般画个图,问题就迎刃而解了~

以head = [1,2,3,4,5], k = 3为例,图画的可能有点丑,但是可能会对你有点帮助。

递归示意图

这里k=3,因此只会循环三次,当最后循环完curr的位置是在头结点,head的位置在temp,因此后面需要将head=curr。最后返回head即可。

迭代

        int n = 0;
//        统计链表长度
        for (ListNode i = head; i != null; n++, i = i.next);

        ListNode dmy = new ListNode(0);
        dmy.next = head;
        for(ListNode prev = dmy, tail = head; n >= k; n -= k) {
            for (int i = 1; i < k; i++) {
                ListNode next = tail.next.next;
                tail.next.next = prev.next;
                prev.next = tail.next;
                tail.next = next;
            }

            prev = tail;
            tail = tail.next;
        }
        return dmy.next;

迭代的代码可能会有点绕,xx.next.next这种样式,不要有恐惧感,看懂之后思路还是很清晰的。

前面两行代码是用n统计链表长度。其中遍历链表的写法还是很新颖的,值得借鉴。

下面在head前面构造了一个哨兵节点dummy,然后以dummy为prev,head为tail就开始翻转了。

for(ListNode prev = dmy, tail = head; n >= k; n -= k)这块代码是将链表按k个一组开始进行翻转。

里面的for (int i = 1; i < k; i++)这里是从1开始而不是0开始,感觉会少循环一次。我是这么理解的,当有k个数,其实真正翻转的时候只会翻转k-1次,这里1是没问题的。

具体的循环思路可以参考下图,画的有点乱,我相信你能看懂的~

迭代示意图

一开始prev和tail是相邻的,tail.next.next=prev.next其实就是指将tail的下一个节点指向prev的下一个节点,注意是tail的下一个节点不是tail。

然后prev.next=tail.next是指prev指向tail的下一个节点。

tail.next=next是指tail指向原来tail.next.next位置,这里不直接用tail.next.next是因为这个指针现在已经断掉了,所以用事先保存好的next。

在k个一组翻转完会将prev和tail进行重新赋值,在进行下一轮。

最后返回的是最开始的head节点位置,只是由于head现在已经移动不知道到哪里去了,所以用的是dummy.next,其实就是最开始的head位置。

写在最后

这三个解法主要掌握递归和迭代即可,栈那个其实没啥意义,时间复杂度太高,实际很难用到,除非什么特殊场景。

迭代和递归写法其实都很绕,经常会出现今天会写,过一周看又有点不会了,所以多看多写吧,没啥办法,加油!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,802评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,109评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,683评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,458评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,452评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,505评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,901评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,550评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,763评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,556评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,629评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,330评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,898评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,897评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,140评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,807评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,339评论 2 342

推荐阅读更多精彩内容