Leetcode总结 -- 链表

目录

链表的基本操作

  • 改/遍历:while(?)
  • 查: 返回倒数K个节点
  • 增/删除:反转链表,删除链表中的重复节点II

链表的一种重要题型 : 快慢指针

  • 回环链表
  • 环路检测
  • 将有序链表转换成平衡BST

PART I : 链表的基本操作

改/遍历

因为链表不是像数组一样占用了连续地址的结构,所以很难通过索引(index)的方式进行存取,同时我们也没有办法不通过一次遍历就知道链表的元素个数。它的访问和修改往往需要通过通过一个指针,并非C语言意义上的指针(pointer),而更应该被称作游标(cursor),如果没有说明,下文还是通过指针这个词代指链表中访问节点的指示器。

        public class ListNode {
                int val;
                ListNode next;
                ListNode(int x) { val = x; }
          }

              ListNode cur = head;
               while (cur != null) {
                    cur = cur.next;
                }

比如,向链表中结尾后面新增加一个节点,节点的值为-1:


    public void addEnd(ListNode head){
        ListNode cur = head;
        while(cur.next != null){
            cur = cur.next;
        }
        cur .next = new ListNode(-1);
    }

再比如,向链表(链表的元素是不重复的)中的元素4前面增加一个数值为6的节点:


 public void addBefore4(ListNode head){
        ListNode cur = head;
        while(cur.next.val != 4){
            cur = cur.next;
        }
        ListNode temp = cur.next;
        cur.next = new ListNode(6);
        cur.next.next = temp;
    }

上面几个例子的难点都在于:while(?)while语句中需要填什么内容?

while(?)

什么时候使用cur.next 什么时候用cur?这个地方涉及到对while()语句的理解。我们可以形象地引入英语中的两个概念,英语的将来时(cur.next)和完成时(cur)。
cur.next!=null这个判断条件表示当跳出while循环时,cur.next == null,即cur 要将链表迭代完,而cur == null ,表示指针cur已经将链表迭代完成。

cur != null 同理:

至于while的条件判断填什么内容,一般来说,如果是单纯迭代链表的话,使用cur!=null,如果想做插入,删除等等操作,需要利用被插入/删除的前一个节点的的话,则需要cur.next != null

下面看看两个题目,加深对while(?)这个问题的理解:

反转链表

反转链表

第一种思路:头插法

头插法的时候需要使用在做链表相关问题中的一个非常重要的性质,dummy节点(空头结点),它的重要性,后面都会有体现:

头插法的示意图

 public ListNode reverseList(ListNode head) {
            ListNode dummy = new ListNode(0);
            ListNode cur = head;
            while(cur!= null){
                ListNode node = new ListNode(cur.val);
                node.next = dummy.next;
                dummy.next = node;
                cur = cur.next;
            }
            return dummy.next;
        }

这种方法在实现过程中,其实吧原来的链表head,重新复制了它的值,创建了新的链表,内存开销很大。

第二种思路:交换元素法

具体代码如下:

 public ListNode reverseList(ListNode head){
            ListNode cur = head;
            ListNode pre=  null;
            while(cur != null){
                ListNode temp = cur.next;
                cur.next = pre;
                pre = cur;
                cur = temp;
            }
            return pre;
        }

返回倒数K节点

思路很简单,通过两个指针就可以确定一个“标尺”,通过这个标尺往下走,直到标尺的底端碰到链表的最后一个节点。很明显,当K=2时,因为链表的最后一个节点是5(本题中),倒数第二个节点应该是4,应当使用“将来时”,即cur.next != null
返回倒数K节点

代码如下:

class Solution {
        public int kthToLast(ListNode head, int k) {
            ListNode cur = head;
            while( -- k > 0)
                cur = cur.next;
            ListNode p = head;
            while(cur.next != null){
                cur = cur.next;
                p = p.next;
            }
            return p.val;
        }
    }

删除排序链表中的重复节点II

此外,大家可能对dummy节点的使用理解不够深入,这里再给出一道题:
删除排序链表中的重复节点II
排序链表的重复节点一定是相邻的,根据题意,一旦有重复的节点就会全部删除。所以我们很容易想到,通过一个指针cur记录当前节点的位置,cur.nextcur.next.next比较后面有没有节点重复,如果一旦有重复,就通过temp节点搜索到没有重复的节点,直接连接到cur的下一个:

这里用的是指针的完成时,所以可以看到当指针cur.next指向temp的时候并没有消除2和3之间的指针。不给过这个题这样写也是ac的,如果要改掉这里的话可以使用指针的将来时。

 public ListNode deleteDuplicates(ListNode head) {
            ListNode dummy = new ListNode(Integer.MAX_VALUE);
            dummy.next = head;
            ListNode cur = dummy;
            while(cur.next != null && cur.next.next!= null){
                if(cur.next.val == cur.next.next.val){
                    ListNode temp = cur.next;
                    while(temp != null && temp.val == cur.next.val){
                        temp = temp.next;
                    }
                    cur.next = temp;
                }else
                    cur = cur.next;
            }
            return dummy.next;
        }

PART II: 快慢指针

直接通过一道题目来解释快慢指针的概念:
环路检测

如果在链表中存在环,则返回环的入口,在这个题目中就是第三个节点。这道题可以分解成两个子问题:判断链表是否存在环路,如果存在环路,返回环路的入口。

判断是否存在环路

闪电侠和你在一个环形的赛道上跑步,你刚迈出腿,闪电侠就和你相遇了。

如果链表中存在环的话,存在这一快一慢两个指针,这两个指针在若干次迭代后必然相遇。

直接看看代码:

class Solution {
        public boolean hasCycle(ListNode head) {
            if(head == null || head.next == null) return false;
            ListNode slow = head;
            ListNode fast = head.next;
            while(slow != fast){
                if(fast == null || fast.next == null) return false;
                slow = slow.next;
                fast = fast.next.next;
            }
            return true;
        }
    }

但是一般情况下,快慢指针使用的代码模板是这样的:

//完成时 后中
   ListNode slow = head, fast = head;
            while(fast != null && fast.next != null){
                slow = slow.next;
                fast = fast.next.next;
            }
//将来时 前中
   ListNode slow = head, fast = head;
            while(fast.next != null && fast.next.next != null){
                slow = slow.next;
                fast = fast.next.next;
            }

我们下面来分析这段代码.

完成时+当链表节点为偶数时:

很明显,当迭代刚刚结束时,slow恰好在偶数链表的中间的后半部分,所以这样的情况我称之为“后中”。

完成时+当链表节点为奇数时:

slow节点指向的是链表的正中间节点

将来时+当链表节点为偶数时:

与上面的相对应的,这中情况被称为前中。

将来时+当链表节点为奇数时:

和上面完全相同。所以我们可以知道,指针的将来时和完成时分别影响的是slow指针的位置,分成了“前中”和“后中”两种情况。具体的我们再来分析。

如何找出环路的入口

大致上知道了快慢指针的找环是否存在的流程之后,我们看看如何找到入口。
设慢指针slow在和快指针fast第一次相遇走过的节点的距离为ls,快指针走过的距离为lf,其中m为链表的第一个节点到环形入口的距离,环的距离为r,环的入口到两个指针相遇的距离为d,如图,则有以下的关系:

ls = m + d;
lf = m + d + n*r

快指针可能在整个环内走了n圈,但是因为快慢指针同时出发,两个指针也满足如下关系:

ls*2 = lf 
m + d = n * r
m  = (n-1)*r  + r - d

当快慢指针相遇时,如果把慢指针放回起点,快指针在相遇点继续走,当慢指针走完m,快指针就回走完r-d,再次相遇时就是链表的入口,代码如下:

public class Solution {
        public ListNode detectCycle(ListNode head) {
            if(head == null || head.next == null) return null;
            ListNode slow = head;
            ListNode fast = head;
            while(fast != null && fast.next!= null){
                slow = slow.next;
                fast = fast.next.next;
                if(slow == fast) break;
            }
            if(fast == null || fast.next == null) return null;
            slow = head;
            while(slow != fast){
                slow = slow.next;
                fast = fast.next;
            }
            return slow;
        }
    }

这里采用了fast的完成时,如果没有回环的话直接遍历完毕整个链表,退出while

此外快慢指针的应用除了检测环路之外,还可以利用上述的“前中”或者“后中”,找到链表的中间节点。

将有序链表变成平衡二叉搜索树

要想变成平衡二叉搜索树肯定要找中间的节点。使用后中法或者前中法都行,这里使用的是的后中法:
将有序链表变成平衡二叉搜索树

class Solution {

        public ListNode findMiddleElement(ListNode node){
            ListNode prev = null;
            ListNode slow = node;
            ListNode fast = node;
            while(fast != null && fast.next != null){
                prev = slow;
                slow = slow.next;
                fast = fast.next.next;
            }
            if(prev != null) prev.next = null;
            return slow;
        }

        public TreeNode sortedListToBST(ListNode head) {
            if(head == null) return null;
            ListNode mid = findMiddleElement(head);

            if(mid == head) return new TreeNode(mid.val);

            TreeNode midTreeNode = new TreeNode(mid.val);
            midTreeNode.left = sortedListToBST(head);
            midTreeNode.right = sortedListToBST(mid.next);
            return midTreeNode;
        }
    }

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