LeetCode刷题之链表

链表(Linked List)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer)。

由于不必须按顺序存储,链表在插入的时候可以达到 O(1)的复杂度,比另一种线性表 —— 顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要 O(n)的时间,而顺序表相应的时间复杂度分别是 O(log\ n)和O(1)。

链表允许插入和移除表上任意位置上的节点,但是不允许随机存取。链表有很多种不同的类型:单向链表,双向链表以及循环链表。

各题题解:

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

//            ###### [反转链表](https://leetcode-cn.com/problems/reverse-linked-list/) ★
    /**
     * 思路: 双指针迭代
     * 我们可以申请两个指针,第一个指针叫 pre,最初是指向 null 的。
     * 第二个指针 cur 指向 head,然后不断遍历 cur。
     * 每次迭代到 cur,都将 cur 的 next 指向 pre,然后 pre 和 cur 前进一位。
     * 都迭代完了(cur最后一次会被置为null),pre 就是最后一个节点了。
     * @param head
     * @return
     */
    public ListNode reverseList(ListNode head) {
        ListNode preNode = null;
        ListNode curNode = head;
        while (curNode != null) {
            //核心就是4步
            //1.next往后移,2.cur.next指向pre
            //3.pre往后移,4.cur往后移
            //pre保持指向前一个节点,cur、next指向当前同一个节点
            ListNode temp = curNode.next;
            curNode.next = preNode;
            //preNode/curNode节点往后移动
            preNode = curNode;
            curNode = temp;
        }
        return preNode;
    }

//            ###### [反转链表 II](https://leetcode.cn/problems/reverse-linked-list-ii/) ★
    /**
     * 根据上图思路:
     * 1.需要快进到反转区间左位置
     * 2.使用变量记住区间前一个位置和第一个位置节点
     * 3.使用经典四步反转left到right区间的节点
     * 4.将反转后的链表与原链表进行拼接
     */
    public ListNode reverseBetween(ListNode head, int left, int right) {

        ListNode pre = null, cur = head;
        //cur在pre的前一个位置
        for (int i=0;i<left-1;i++) {
            pre = cur;
            cur = cur.next;
        }
        //使用变量记住区间前一个位置和第一个位置节点
        ListNode pre2 = pre;
        ListNode cur2 = cur;
        for (int i=left;i<=right;i++) {
            ListNode temp = cur.next;
            cur.next = pre;
            pre = cur;
            cur = temp;
        }
        //将反转后的链表与原链表进行拼接

        //有一种情况,从第一个位置就开始反转,那么pre开始为null,pre2也是为null的,需要判空一下,头就指向pre,即反转区间的头节点
        if (pre2 != null) {
            pre2.next = pre;
        } else {
            head = pre;
        }
        cur2.next = cur;
        return head;
    }

//            ###### [环形链表](https://leetcode-cn.com/problems/linked-list-cycle/) ★
    //快慢指针:快指针单位时间移动2步,慢指针单位时间移动1步
    //如果某个时候,快指针的next为null了,那么表示链表有尾节点
    //否则,去判断,一定在某一时刻,快指针会超过慢指针N圈,站在同一个位置
    public boolean hasCycle(ListNode head) {
        if(head == null || head.next == null) {
            return false;
        }

        ListNode slow = head;
        ListNode fast = head;

        while(fast != null && fast.next != null) {
            //要么有环,要么没有环,没有环,则最终快速指针会走完,如果没有环,
            //则快速指针一定会在某个时刻追上慢速指针,即指向同一个节点
            //这里要找到一个循环结束点,并返回true,否则会死循环

            fast = fast.next.next;
            slow = slow.next;
            if(fast == slow) {
                return true;
            }
        }
        return false;
    }

//            ###### [环形链表 II](https://leetcode.cn/problems/linked-list-cycle-ii/)
    public ListNode detectCycle(ListNode head) {
        //题解:这个题的目标,是找到环的入环点,
        // 根据上图,首先根据公式推导出  a = (n-1)(b+c) + c,通过这个公式,
        // 再用两个指针相同速度走,相遇点就是要返回的入环节点
        //那么第一步:定义一个快指针和慢指针,快指针每步走2个,慢指针每步走一个,在有环的情况下快指针一定会追上慢指针相遇
        //得到相交点,构成这幅图的形式

        ListNode slow = head, fast = head;
        while(fast != null) {
            //让慢指针每次走1步,快指针每次走2步
            slow = slow.next;
            if (fast.next != null) {
                fast = fast.next.next;
            } else {
                return null;
            }
            if (slow == fast) {
                //再构造两个慢指针走到入环节点
                ListNode cur = head;
                while(cur != slow) {
                    cur = cur.next;
                    slow = slow.next;
                }
                return cur;
            }
        }
        return null;
    }

//            ###### [回文链表](https://leetcode-cn.com/problems/palindrome-linked-list/) ★
    public boolean isPalindrome(ListNode head) {
        ArrayList<Integer> arrayList = new ArrayList<>();
        ListNode cur = head;
        while(cur != null) {
            arrayList.add(cur.val);
            cur = cur.next;
        }
        int start = 0, end = arrayList.size()-1;
        while(start < end) {
            //将对应位置的数进行对比,只要不相等,就不是回文链表,直到start==end
            if (arrayList.get(start) != arrayList.get(end)) {
                return false;
            }
            start++;
            end--;
        }
        return true;
    }

//            ###### [合并两个有序链表](https://leetcode-cn.com/problems/merge-two-sorted-lists/) ★
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode curNode = new ListNode(-1);
        ListNode head = curNode;
        while(l1!=null && l2!=null) {
            if(l1.val <= l2.val) {
                curNode.next = l1;
                l1 = l1.next;
            } else {
                curNode.next = l2;
                l2 = l2.next;
            }
            curNode = curNode.next;
        }
        curNode.next = l1==null?l2:l1;
        return head.next;
    }

//            ######[删除链表的倒数第 N 个结点](https://leetcode.cn/problems/remove-nth-node-from-end-of-list/)

    /**
     * 思路:双指针法
     * 1.创建虚拟节点dummy,指向head;left节点指向dummy,用来占删除位置的前一个位置
     * 2.right节点指向head,将right节点移动n个位置,然后left和right继续同时往后移,直到right到达链表末尾
     * 3.将left的next指向next.next
     */
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummy = new ListNode(0);
        dummy.next = head;
        ListNode left = dummy;  //left需要占删除位置的前一个位置
        ListNode right = head;
        for (int i=0;i<n;i++) { //拉开lfet 和 right节点 n个位置 ,参考链表的倒数第k个节点题
            right = right.next;
        }
        while(right != null) {  //让right节点走到链表末尾,left到达倒数第N个节点前一个节点
            right = right.next;
            left = left.next;
        }
        left.next = left.next.next;
        return dummy.next;
    }


//            ###### [相交链表](https://leetcode-cn.com/problems/intersection-of-two-linked-lists/)
//创建两个指针 pApA 和 pBpB,分别初始化为链表 A 和 B 的头结点。然后让它们向后逐结点遍历。
//当 pApA 到达链表的尾部时,将它重定位到链表 B 的头结点 (你没看错,就是链表 B); 类似的,当 pBpB 到达链表的尾部时,将它重定位到链表 A 的头结点。
//若在某一时刻 pApA 和 pBpB 相遇,则 pApA/pBpB 为相交结点。
//为什么这种方法是可行的,因为当A、B都走到公共节点的时候,正好走了自己的路,又走了对方的路,两个指针走的距离相等
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode n1 = headA;
        ListNode n2 = headB;
        while(n1 != n2) {
            if(n1 != null) {
                //有后面节点,继续往后移动
                n1 = n1.next;
            } else {
                n1 = headB;
            }

            if(n2 != null) {
                n2 = n2.next;
            } else {
                n2 = headA;
            }
        }

        //当n1==n2了,即各自走了一圈半到了第一个相交节点,返回n1或n2都可以
        return n1;
    }

//            ######[两数相加](https://leetcode.cn/problems/add-two-numbers/)

    /**
     * 思路:模拟两个数字进行相加
     * 我们同时遍历两个链表,逐位计算它们的和,并与当前位置的进位值相加。
     * 具体而言,如果当前两个链表处相应位置的数字为 n1,n2,进位值为 carry,则它们的和为 n1+n2+carry;
     * 其中,,答案链表处相应位置的数字为 (n1+n2+carry) % 10,而新的进位值为 (n1+n2+carry)/10
     * 如果两个链表的长度不同,则可以认为长度短的链表的后面有若干个0 。
     * 此外,如果链表遍历结束后,有 carry>0,还需要在答案链表的后面附加一个节点,节点的值为 carry。
     */
    public static ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode head = null, tail = null; //新链表的头、尾声明
        int carry = 0; //进位的值
        while(l1 != null || l2 != null) {
            //当两个链表不是都完了,程序就需继续按位相加
            int n1 = l1!=null ? l1.val : 0;
            int n2 = l2!=null ? l2.val : 0;
            int cur = n1+n2+carry; //当前位置相加

            if (head == null) {
                //第一个节点,创建头尾节点指向新节点
                tail = head = new ListNode(cur % 10);
            } else {
                //非第一个节点,让尾结点指向新的节点
                tail.next = new ListNode(cur % 10);
                tail = tail.next;
            }

            carry = cur / 10; //下一个的进位

            //l1 l2都往后移
            if (l1 != null) {
                l1 = l1.next;
            }
            if (l2 != null) {
                l2 = l2.next;
            }

            if (carry > 0) {
                tail.next = new ListNode(carry);
            }
        }
        return head;
    }

//            ######[两两交换链表中的节点](https://leetcode.cn/problems/swap-nodes-in-pairs/)

    /**
     * 操作示例: 1->2->3->4->5
     * 思路:创建一个虚拟节点指向head,cur指向虚拟节点。用temp保存1和3的位置供后面连接。让cur->2, 2->1, 1->3
     * 然后cur往后移动两位,即cur需要在反转的两个节点的前一个节点位置
     */
    public ListNode swapPairs(ListNode head) {
        ListNode dummyHead = new ListNode(0); //虚拟节点
        dummyHead.next = head; //虚拟节点指向head
        ListNode cur = dummyHead;
        ListNode temp1, temp2;
        while (cur.next != null && cur.next.next != null) {
            //循环结束条件:偶数个,cur.next为null结束;奇数个,cur.next.next为null结束
            temp1 = cur.next; //temp1占 1的位置
            temp2 = cur.next.next.next; //temp2占 3的位置
            cur.next = cur.next.next; //cur指向 2的位置
            cur.next.next = temp1; //2指向1的位置
            temp1.next = temp2; //1指向 3的位置

            cur = cur.next.next; //cur往后移动两位
        }
        return dummyHead.next;
    }

//            ######[旋转链表](https://leetcode.cn/problems/rotate-list/)
    /**
     * 思路:记给定链表的长度为 n,注意到当向右移动的次数 k≥n 时,我们仅需要向右移动 k mod n 次即可。因为每 n 次移动都会让链表变为原状。
     * 这样我们可以知道,新链表的最后一个节点为原链表的第 (n−1) - (k mod n) 个节点(从 0 开始计数)。
     * @param head
     */
    public ListNode rotateRight(ListNode head, int k) {
        if (k == 0 || head == null || head.next == null) {
            return head;
        }
        int n = 1;
        ListNode iter = head;
        while (iter.next != null) {
            iter = iter.next;
            n++;
        }
        int add = n - k % n;
        if (add == n) {
            return head;
        }
        iter.next = head;
        while (add-- > 0) {
            iter = iter.next;
        }
        ListNode ret = iter.next;
        iter.next = null;
        return ret;
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容