LeetCode 專題:單鏈表

知識點總結(jié)

1诅妹、鏈表問題只要涉及到頭結(jié)點的操作的,一般都會用到設置虛擬頭結(jié)點這個技巧;

2吭狡、鏈表中的問題尖殃,很多可以歸結(jié)為“穿針引線”,“穿針引線”要寫正確的一個重要技巧就是“畫圖”划煮,“畫圖”會使自己的思路更加清晰送丰,這一點是非常重要的;

3弛秋、鏈表中可以遞歸處理的問題有:(1)合并有序鏈表蚪战;(2)刪除鏈表中的結(jié)點;(3)反轉(zhuǎn)鏈表铐懊;(4)兩兩交換鏈表中的結(jié)點邀桑。

4、有的時候科乎,鏈表的操作壁畸,有循環(huán)或者判斷的,一定要記得看一看循環(huán)或者判斷的循環(huán)體或者判斷邏輯茅茂,就能檢查出自己的代碼是否寫得正確捏萍,例如 LeetCode 第 148 題,使用“歸并排序”將單鏈表進行排序和“單鏈表找中點”空闲。

下面這句話不一定對令杈,暫且先放在這里:鏈表的動態(tài)特性,很適合用來實現(xiàn)棧碴倾。數(shù)組適合在尾部操作逗噩,要擴容、縮容跌榔。鏈表適合在頭部操作异雁,每次都要 new 一個內(nèi)存空間。所以僧须,其實不論是數(shù)組實現(xiàn)還是鏈表實現(xiàn)纲刀,都沒有本質(zhì)區(qū)別。

反轉(zhuǎn)鏈表

LeetCode 第 206 題:反轉(zhuǎn)鏈表

LeetCode 第 92 題:反轉(zhuǎn)從位置 m 到 n 的鏈表担平,k 個組進行一次反轉(zhuǎn)

穿針引線示绊。

LeetCode 第 86 題:分割鏈表

不難,不過一直搞不清楚這個問題的意思暂论。

LeetCode 第 2 題面褐、第 445 題:鏈表求和

LeetCode 第 2 題:兩數(shù)相加

要求:給出兩個 非空 的鏈表用來表示兩個非負的整數(shù)。其中空另,它們各自的位數(shù)是按照 逆序 的方式存儲的盆耽,并且它們的每個節(jié)點只能存儲 一位 數(shù)字蹋砚。

如果扼菠,我們將這兩個數(shù)相加起來摄杂,則會返回一個新的鏈表來表示它們的和。

您可以假設除了數(shù)字 0 之外循榆,這兩個數(shù)都不會以 0 開頭析恢。

分析:這道題其實不難,主要考察了代碼的編寫能力秧饮。

1映挂、首先應該考慮到邊界情況:

if l1 is None:
    return l2

if l2 is None:
    return l1

2、鏈表問題一般都會設置虛擬頭結(jié)點盗尸;

3柑船、進位問題有模板寫法:模 10 得到位數(shù),再除以 10 得到進位泼各;

4鞍时、最后考慮要不要進位 1

# Definition for singly-linked list.
class ListNode:
    def __init__(self, x):
        self.val = x
        self.next = None


class Solution:
    def addTwoNumbers(self, l1, l2):
        """
        :type l1: ListNode
        :type l2: ListNode
        :rtype: ListNode
        """
        if l1 is None:
            return l2

        if l2 is None:
            return l1

        dummy_node = ListNode(-1)
        cur_node = dummy_node
        s = 0

        # 只要二者之一非空扣蜻,就加下去
        while l1 or l2:
            if l1:
                s += l1.val
                l1 = l1.next
            if l2:
                s += l2.val
                l2 = l2.next
            cur_node.next = ListNode(s % 10)
            s //= 10
            cur_node = cur_node.next
        if s == 1:
            cur_node.next = ListNode(1)
        return dummy_node.next

問題并不難逆巍,但是編碼上要注意。轉(zhuǎn)換成數(shù)字不是不可以莽使,但是鏈表很長的時候锐极,就有越界的風險。

思考為什么要使用虛擬頭結(jié)點芳肌,計算 carry 是一個常見的關(guān)于進位的操作灵再。第 445 題稍微復雜,要借助棧完成亿笤。

LeetCode 第 445 題:兩數(shù)相加 II

傳送門:445. 兩數(shù)相加 II檬嘀。

刪除鏈表中等于給定值 val 的所有結(jié)點

LeetCode 第 203 題:刪除鏈表中等于給定值 val 的所有結(jié)點

一個對于鏈表常用的操作就是設置虛擬頭結(jié)點。

LeetCode 第 203 題:移除鏈表元素

傳送門:移除鏈表元素责嚷。

只要涉及跟鏈表頭結(jié)點有關(guān)的鸳兽,都要設計虛擬頭結(jié)點來避免繁瑣的討論。

LeetCode 第 82 題

傳送門:刪除排序鏈表中的重復元素 II罕拂。

LeetCode 第 83 題

傳送門:刪除排序鏈表中的重復元素揍异。

和 82 題完全不同。

分析:這道題給出兩種解法爆班。
相同之處:

  • 都使用了當前指針和當前指針的下一指針衷掷,一共兩個指針來幫助我們完成邏輯。
  • 當前指針的下一指針和"當前指針"作判斷柿菩,如果一樣戚嗅,說明應該丟棄"當前指針的下一指針",如果一樣,說明應該保留"當前指針的下一指針"懦胞。
    不同之處在于如何保留:
  • 發(fā)現(xiàn)一樣的元素的時候替久,馬上把 next 指針后移;
  • 發(fā)現(xiàn)一樣的元素的時候躏尉,馬上把 current 指針挪到 next 指針的下一位蚯根。

關(guān)鍵點:

  • 保證在遍歷過程中保持循環(huán)不變量的定義:current 總是指向第 1 次出現(xiàn)該數(shù)組的位置,next 用于遍歷胀糜,會走過所有的元素颅拦。

解法1:

提示:要考慮到一些特殊情況,才能寫好邏輯教藻。

public class Solution2 {

    /**
     * 從一個有序的鏈表中刪除重復的元素
     * 思路:從后面往前面看距帅,有重復就刪除
     *
     * @param head
     * @return
     */
    public ListNode deleteDuplicates(ListNode head) {
        if(head==null){
            return null;
        }
        ListNode cur = head;
        ListNode next = cur.next;

        while (next!=null){
            if(next.val == cur.val){ //  1,1,1,1,2,3
                // next 后移一位
                next = next.next;
            }else { // 1,2,3
                cur.next = next; // 把 cur 的指針指向下一位
                cur = next; // 移動指針
                next = next.next;
            }
        }
        // 這一行是一個要小心的陷阱
        cur.next = null;
        return head;
    }

    public static void main(String[] args) {
        ListNode head1 = createListNode(new int[]{1,1,2,2,2,2,3,3,3,3});
        Solution2 solution2 = new Solution2();
        ListNode head2 = solution2.deleteDuplicates(head1);
        printLinkedList(head2);

    }


    public static ListNode createListNode(int[] nums) {
        if (nums.length == 0) {
            return null;
        }

        ListNode head = new ListNode(nums[0]);
        ListNode curNode = head;

        for (int i = 1; i < nums.length; i++) {
            curNode.next = new ListNode(nums[i]);
            curNode = curNode.next;
        }
        return head;
    }


    // 超級簡單的一個工具方法
    public static void printLinkedList(ListNode head) {
        ListNode curNode = head;
        while (curNode != null) {
            System.out.printf("%s\t", curNode.val);
            curNode = curNode.next;
        }
        System.out.printf("null");
    }
}

解法2

public class Solution3 {
    public ListNode deleteDuplicates(ListNode head) {
        if (head == null) {
            return null;
        }
        ListNode cur = head;
        ListNode next = cur.next;
        while (next != null) {
            if (next.val == cur.val) { // 1,1,2
                // 因為 next 不被使用了,所以 cur 的下一個就指向 next 的下一個
                cur.next = next.next;
                // 此時不能移動 cur括堤,因為 cur 一定是第 1 個出現(xiàn)的元素锥债,注意這里保持循環(huán)不變量的定義
                next = next.next;
            } else { // 1,2,3
                cur = next;
                next = next.next;
            }
        }
        return head;
    }

    public static void main(String[] args) {
        ListNode head1 = createListNode(new int[]{1, 1, 2, 2, 2, 2, 3, 3, 3, 3});
        Solution3 solution3 = new Solution3();
        ListNode head2 = solution3.deleteDuplicates(head1);
        printLinkedList(head2);
    }

    public static ListNode createListNode(int[] nums) {
        if (nums.length == 0) {
            return null;
        }
        ListNode head = new ListNode(nums[0]);
        ListNode curNode = head;
        for (int i = 1; i < nums.length; i++) {
            curNode.next = new ListNode(nums[i]);
            curNode = curNode.next;
        }
        return head;
    }

    // 超級簡單的一個工具方法
    public static void printLinkedList(ListNode head) {
        ListNode curNode = head;
        while (curNode != null) {
            System.out.printf("%s\t", curNode.val);
            curNode = curNode.next;
        }
        System.out.printf("null");
    }
}

LeetCode 第 328 題:奇數(shù)(Odd)偶數(shù)(Even)鏈表

傳送門: https://leetcode.com/problems/odd-even-linked-list/description/

思路:在遍歷的過程中,標記奇數(shù)節(jié)點和偶數(shù)節(jié)點痊臭,把奇數(shù)節(jié)點和偶數(shù)節(jié)點分開哮肚。最后把奇數(shù)節(jié)點的最后一個節(jié)點指向偶數(shù)節(jié)點的開始節(jié)點,具體細節(jié)請見代碼广匙。

我的解答:

class ListNode {
    int val;
    ListNode next;

    ListNode(int x) {
        val = x;
    }
}

public class Solution {

    public ListNode oddEvenList(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        ListNode oddHead = head;
        ListNode evenHead = oddHead.next;
        ListNode oddNode = oddHead;
        ListNode evenNode = evenHead;
        ListNode currentNode = evenHead.next;
        boolean isodd = true;
        while (currentNode != null) {
            if (isodd) {
                oddNode.next = currentNode;
                oddNode = currentNode;
            } else {
                evenNode.next = currentNode;
                evenNode = currentNode;
            }
            isodd = !isodd;
            currentNode = currentNode.next;
        }
        isodd = !isodd;
        if (isodd) {
            oddNode.next = evenHead;
            evenNode.next = null;
        } else {
            oddNode.next = evenHead;
        }
        return oddHead;
    }


    public static void main(String[] args) {
        ListNode node1 = createListNode(new int[]{1, 2, 3, 4, 5});
        Solution solution = new Solution();
        ListNode result1 = solution.oddEvenList(node1);
        printLinkedList(result1);

        System.out.println("------");


        ListNode node2 = createListNode(new int[]{1, 2, 3, 4});
        ListNode result2 = solution.oddEvenList(node2);
        printLinkedList(result2);

        System.out.println("------");


        ListNode node3 = createListNode(new int[]{1, 2});
        ListNode result3 = solution.oddEvenList(node3);
        printLinkedList(result3);
    }

    public static ListNode createListNode(int[] nums) {
        if (nums.length == 0) {
            return null;
        }
        ListNode head = new ListNode(nums[0]);
        ListNode curNode = head;
        for (int i = 1; i < nums.length; i++) {
            curNode.next = new ListNode(nums[i]);
            curNode = curNode.next;
        }
        return head;
    }

    // 超級簡單的一個工具方法
    public static void printLinkedList(ListNode head) {
        ListNode curNode = head;
        while (curNode != null) {
            System.out.printf("%s\t", curNode.val);
            curNode = curNode.next;
        }
        System.out.printf("null");
    }
}

網(wǎng)友的解答:http://blog.csdn.net/guicaisa/article/details/50557475
顯然允趟,網(wǎng)友的解法會更簡潔一些:
根據(jù)網(wǎng)友的解答自己寫了一遍:

public class Solution2 {

    public ListNode oddEvenList(ListNode head) {
        if (head == null) {
            return head;
        }
        ListNode oddNode = head;
        ListNode evenNode = head.next;
        ListNode evenHead = evenNode;
        while (evenNode != null && evenNode.next != null) {
            oddNode.next = evenNode.next;
            oddNode = oddNode.next;
            evenNode.next = oddNode.next;
            evenNode = evenNode.next;
        }
        oddNode.next = evenHead;
        return head;
    }


    public static void main(String[] args) {
        ListNode node1 = createListNode(new int[]{1, 2, 3, 4, 5});
        Solution2 solution = new Solution2();
        ListNode result1 = solution.oddEvenList(node1);
        printLinkedList(result1);

        System.out.println("------");


        ListNode node2 = createListNode(new int[]{1, 2, 3, 4});
        ListNode result2 = solution.oddEvenList(node2);
        printLinkedList(result2);

        System.out.println("------");


        ListNode node3 = createListNode(new int[]{1, 2});
        ListNode result3 = solution.oddEvenList(node3);
        printLinkedList(result3);
    }

    public static ListNode createListNode(int[] nums) {
        if (nums.length == 0) {
            return null;
        }
        ListNode head = new ListNode(nums[0]);
        ListNode curNode = head;
        for (int i = 1; i < nums.length; i++) {
            curNode.next = new ListNode(nums[i]);
            curNode = curNode.next;
        }
        return head;
    }

    // 超級簡單的一個工具方法
    public static void printLinkedList(ListNode head) {
        ListNode curNode = head;
        while (curNode != null) {
            System.out.printf("%s\t", curNode.val);
            curNode = curNode.next;
        }
        System.out.printf("null");
    }
}

LeetCode 第 147 題:單鏈表的插入排序

重刷

LeetCode 第 148 題:單鏈表的排序,使用歸并排序

LeetCode 第 24 題:兩兩交換鏈表中的結(jié)點

LeetCode 第 25 題:k 個一組交換鏈表

重刷

LeetCode 第 138 題:復制帶隨機指針的鏈表

LeetCode 第 109 題:有序鏈表轉(zhuǎn)換二叉搜索樹

英文網(wǎng)址:109. Convert Sorted List to Binary Search Tree 鸦致,中文網(wǎng)址:109. 有序鏈表轉(zhuǎn)換二叉搜索樹 潮剪。

LeetCode 第 237 題:刪除鏈表中的結(jié)點

要求:請編寫一個函數(shù),使其可以刪除某個鏈表中給定的(非末尾的)結(jié)點分唾,您將只被給予要求被刪除的結(jié)點抗碰。

LeetCode 第 21 題:合并兩個有序鏈表

LeetCode 第 21 題:合并兩個有序鏈表

使用遞歸解決穿針引線,單雙轉(zhuǎn)換

穿針引線:

image-20190111135807481

遞歸寫法:首先先寫好遞歸終止條件绽乔。

image-20190111135825172

LeetCode 第 23 題:合并 K 個排序鏈表

英文網(wǎng)址:23. Merge k Sorted Lists 弧蝇,中文網(wǎng)址:23. 合并K個排序鏈表

分析:歸并多個有序鏈表(多路歸并排序)折砸,要用到優(yōu)先隊列

LeetCode 第 19 題:刪除鏈表的倒數(shù)第 N 個結(jié)點

LeetCode 第 61 題:旋轉(zhuǎn)鏈表

LeetCode 第 143 題:重排鏈表

LeetCode 第 234 題:回文鏈表

LeetCode 第 141 題:找出鏈表中是否有環(huán)

LeetCode 第 142 題:找出鏈表的入口結(jié)點

LeetCode 第 114 題:二叉樹展開為鏈表

要求:給定一個二叉樹看疗,原地將它展開為鏈表。

例如睦授,給定二叉樹

    1
   / \
  2   5
 / \   \
3   4   6

將其展開為:

1
 \
  2
   \
    3
     \
      4
       \
        5
         \
          6

Java 代碼:后序遍歷

class Solution {
    public void flatten(TreeNode root) {
        if(root == null){
            return ;
        }
        
        flatten(root.left);
        flatten(root.right);
        
        if(root.left != null){
            TreeNode right = root.right;//記錄右結(jié)點
            root.right = root.left;
            root.left = null;//將左結(jié)點置空
            TreeNode node = root.right;//到左結(jié)點的最后一個結(jié)點
            while(node.right != null){
                node = node.right;
            }
            node.right = right; 
        }
    }
}

鏈表的中間結(jié)點

LeetCode 第 876 題:單鏈表的一個常見操作两芳,設置快慢指針。


LeetCode 第 61 題:鏈表向右旋轉(zhuǎn)

傳送門:61. 旋轉(zhuǎn)鏈表去枷。

給定一個鏈表怖辆,旋轉(zhuǎn)鏈表是复,將鏈表每個節(jié)點向右移動 k 個位置,其中 k 是非負數(shù)竖螃。

示例 1:

輸入: 1->2->3->4->5->NULL, k = 2
輸出: 4->5->1->2->3->NULL
解釋:
向右旋轉(zhuǎn) 1 步: 5->1->2->3->4->NULL
向右旋轉(zhuǎn) 2 步: 4->5->1->2->3->NULL

示例 2:

輸入: 0->1->2->NULL, k = 4
輸出: 2->0->1->NULL
解釋:
向右旋轉(zhuǎn) 1 步: 2->0->1->NULL
向右旋轉(zhuǎn) 2 步: 1->2->0->NULL
向右旋轉(zhuǎn) 3 步: 0->1->2->NULL
向右旋轉(zhuǎn) 4 步: 2->0->1->NULL

Python 代碼:

# Definition for singly-linked list.
class ListNode(object):
    def __init__(self, x):
        self.val = x
        self.next = None


class Solution(object):
    def rotateRight(self, head, k):
        """
        :type head: ListNode
        :type k: int
        :rtype: ListNode
        """
        if head is None or k <= 0:
            return head

        # 先看鏈表有多少元素
        node = head
        # 先數(shù)這個鏈表的長度
        counter = 1
        while node.next:
            node = node.next
            counter += 1

        node.next = head
        k = k % counter
        node = head
        # counter - k - 1 可以取一些極端的例子找到規(guī)律
        for _ in range(counter - k - 1):
            node = node.next
        new_head = node.next
        node.next = None
        return new_head

(本節(jié)完)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末淑廊,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子斑鼻,更是在濱河造成了極大的恐慌,老刑警劉巖猎荠,帶你破解...
    沈念sama閱讀 210,914評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件坚弱,死亡現(xiàn)場離奇詭異,居然都是意外死亡关摇,警方通過查閱死者的電腦和手機荒叶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評論 2 383
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來输虱,“玉大人些楣,你說我怎么就攤上這事∠芏茫” “怎么了愁茁?”我有些...
    開封第一講書人閱讀 156,531評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長亭病。 經(jīng)常有香客問我鹅很,道長,這世上最難降的妖魔是什么罪帖? 我笑而不...
    開封第一講書人閱讀 56,309評論 1 282
  • 正文 為了忘掉前任促煮,我火速辦了婚禮,結(jié)果婚禮上整袁,老公的妹妹穿的比我還像新娘菠齿。我一直安慰自己,他們只是感情好坐昙,可當我...
    茶點故事閱讀 65,381評論 5 384
  • 文/花漫 我一把揭開白布绳匀。 她就那樣靜靜地躺著,像睡著了一般炸客。 火紅的嫁衣襯著肌膚如雪襟士。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,730評論 1 289
  • 那天嚷量,我揣著相機與錄音陋桂,去河邊找鬼。 笑死蝶溶,一個胖子當著我的面吹牛嗜历,可吹牛的內(nèi)容都是我干的宣渗。 我是一名探鬼主播,決...
    沈念sama閱讀 38,882評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼梨州,長吁一口氣:“原來是場噩夢啊……” “哼痕囱!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起暴匠,我...
    開封第一講書人閱讀 37,643評論 0 266
  • 序言:老撾萬榮一對情侶失蹤鞍恢,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后每窖,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體帮掉,經(jīng)...
    沈念sama閱讀 44,095評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,448評論 2 325
  • 正文 我和宋清朗相戀三年窒典,在試婚紗的時候發(fā)現(xiàn)自己被綠了蟆炊。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,566評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡瀑志,死狀恐怖涩搓,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情劈猪,我是刑警寧澤昧甘,帶...
    沈念sama閱讀 34,253評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站战得,受9級特大地震影響疾层,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜贡避,卻給世界環(huán)境...
    茶點故事閱讀 39,829評論 3 312
  • 文/蒙蒙 一痛黎、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧刮吧,春花似錦湖饱、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至致讥,卻和暖如春仅仆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背垢袱。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評論 1 264
  • 我被黑心中介騙來泰國打工墓拜, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人请契。 一個月前我還...
    沈念sama閱讀 46,248評論 2 360
  • 正文 我出身青樓咳榜,卻偏偏與公主長得像夏醉,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子涌韩,可洞房花燭夜當晚...
    茶點故事閱讀 43,440評論 2 348

推薦閱讀更多精彩內(nèi)容