簡(jiǎn)單鏈表的合并、刪除、交點(diǎn)、是否有環(huán)

一、將兩個(gè)有序鏈表合并為一個(gè)新的有序鏈表并返回

方法一:迭代
首先,我們?cè)O(shè)定一個(gè)哨兵節(jié)點(diǎn) "prehead" ,這可以在最后讓我們比較容易地返回合并后的鏈表。我們維護(hù)一個(gè) prev 指針,我們需要做的是調(diào)整它的 next 指針。然后,我們重復(fù)以下過(guò)程,直到 l1 或者 l2 指向了 null :如果 l1 當(dāng)前位置的值小于等于 l2 ,我們就把 l1 的值接在 prev 節(jié)點(diǎn)的后面同時(shí)將 l1 指針往后移一個(gè)。否則,我們對(duì) l2 做同樣的操作。不管我們將哪一個(gè)元素接在了后面,我們都把 prev 向后移一個(gè)元素。
在循環(huán)終止的時(shí)候, l1 和 l2 至多有一個(gè)是非空的。由于輸入的兩個(gè)鏈表都是有序的,所以不管哪個(gè)鏈表是非空的,它包含的所有元素都比前面已經(jīng)合并鏈表中的所有元素都要大。這意味著我們只需要簡(jiǎn)單地將非空鏈表接在合并鏈表的后面,并返回合并鏈表。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode preHead = new ListNode(-1);
        ListNode prev = preHead;
        while(l1 != null && l2 != null) {
            if(l1.val <= l2.val) {
                prev.next = l1;
                l1 = l1.next;
            } else {
                prev.next = l2;
                l2 = l2.next;
            }
            prev = prev.next;
        }
        prev.next = l1 == null ? l2 : l1;
        return preHead.next;
    }
}

鏈接:https://leetcode-cn.com/problems/merge-two-sorted-lists/solution/he-bing-liang-ge-you-xu-lian-biao-by-leetcode/

方法二:遞歸

  • 終止條件:兩條鏈表分別名為 l1 和 l2,當(dāng) l1 為空或 l2 為空時(shí)結(jié)束
  • 返回值:每一層調(diào)用都返回排序好的鏈表頭
  • 本級(jí)遞歸內(nèi)容:如果 l1 的 val 值更小,則將 l1.next 與排序好的鏈表頭相接,l2 同理
class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        if (l1 == null) {
            return l2;
        }
        if (l2 == null) {
            return l1;
        }
        if (l1.val <= l2.val) {
            l1.next = mergeTwoLists(l1.next, l2);
            return l1;
        } else {
            l2.next = mergeTwoLists(l1, l2.next);
            return l2;
        }
    }
}

鏈接:https://leetcode-cn.com/problems/merge-two-sorted-lists/solution/hua-jie-suan-fa-21-he-bing-liang-ge-you-xu-lian-bi/

二、給定一個(gè)排序鏈表,刪除所有重復(fù)的元素,使得每個(gè)元素只出現(xiàn)一次。
// 由于輸入的列表已排序,因此我們可以通過(guò)將結(jié)點(diǎn)的值與它之后的結(jié)點(diǎn)進(jìn)行比較來(lái)確定它是否為重復(fù)結(jié)點(diǎn)。如果它是重復(fù)的,我們更改當(dāng)前結(jié)點(diǎn)的 next 指針,以便它跳過(guò)下一個(gè)結(jié)點(diǎn)并直接指向下一個(gè)結(jié)點(diǎn)之后的結(jié)點(diǎn)
class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        ListNode current = head;
        while(current != null && current.next != null) {
            if(current.val == current.next.val) {
                current.next = current.next.next;
            } else {
                current = current.next;
            }
        }
        return head;
    }
}
三、給定一個(gè)鏈表,判斷鏈表中是否有環(huán)。

方法一:哈希表
我們遍歷所有結(jié)點(diǎn)并在哈希表中存儲(chǔ)每個(gè)結(jié)點(diǎn)。如果當(dāng)前結(jié)點(diǎn)為空結(jié)點(diǎn) null(即已檢測(cè)到鏈表尾部的下一個(gè)結(jié)點(diǎn)),那么我們已經(jīng)遍歷完整個(gè)鏈表,并且該鏈表不是環(huán)形鏈表。如果當(dāng)前結(jié)點(diǎn)的引用已經(jīng)存在于哈希表中,那么返回 true(即該鏈表為環(huán)形鏈表)
方法二:快慢指針
首先創(chuàng)建兩個(gè)不同速度的節(jié)點(diǎn),如果兩個(gè)節(jié)點(diǎn)相同時(shí),則代表該鏈表是環(huán)形鏈表;
若不相同時(shí),判斷快指針當(dāng)前節(jié)點(diǎn)/下一個(gè)節(jié)點(diǎn)是否到達(dá)終點(diǎn)(即null),若為null則不是環(huán)形鏈表,否則移動(dòng)節(jié)點(diǎn)

class Solution {
    public boolean hasCycle(ListNode head) {
        /// 1、哈希表
        // Set<ListNode> map = new HashSet<>();
        // while(head != null) {
        //     if(map.contains(head)) {
        //         return true;
        //     } else {
        //         map.add(head);
        //     }
        //     head = head.next;
        // } 
        // return false;

        /// 2、快慢指針
        ListNode slow = head, fast = head.next;
        while (slow != fast) {
            if(fast == null || fast.next == null) {
                return false;
            }
            slow = slow.next;
            fast = fast.next.next;
        }
        return true;
    }
}
四、找到兩個(gè)單鏈表相交的起始節(jié)點(diǎn)。

方法一、暴力遍歷
對(duì)鏈表A中的每一個(gè)結(jié)點(diǎn) a ,遍歷整個(gè)鏈表 B 并檢查鏈表 B 中是否存在結(jié)點(diǎn)和 a 相同。

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        /// 暴力遍歷
        while(headA != null) {
            ListNode temp = headB;
            while(temp != null) {
                if(headA == temp) {
                    return headA;
                } else {
                    temp = temp.next;
                }
            }
            headA = headA.next;
        }
        return headA;
    }
}

方法二、哈希表
遍歷鏈表 A 并將每個(gè)結(jié)點(diǎn)存儲(chǔ)在哈希表中。然后檢查鏈表 B 中的每一個(gè)結(jié)點(diǎn) b 是否在哈希表中。若在,則 b為相交結(jié)點(diǎn)

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        /// 哈希表
        Set<ListNode> set = new HashSet<>();
        while(headA != null || headA.next != null) {
            if(headB == null || headB.next == null) return null;
            if(set.contains(headB)) {
                return headB;
            } else {
                set.add(headA);
            }
            headA = headA.next;
            headB = headB.next;
        }
        return headA;
    }
}

方法三、雙指針

  • 創(chuàng)建兩個(gè)指針 pA 和 pB,分別初始化為鏈表 A 和 B 的頭結(jié)點(diǎn)。然后讓它們向后逐結(jié)點(diǎn)遍歷。
  • 當(dāng) pA 到達(dá)鏈表的尾部時(shí),將它重定位到鏈表 B 的頭結(jié)點(diǎn); 類(lèi)似的,當(dāng) pB 到達(dá)鏈表的尾部時(shí),將它重定位到鏈表 A 的頭結(jié)點(diǎn)。
  • 若在某一時(shí)刻 pA 和 pB 相遇,則 pA/pB 為相交結(jié)點(diǎn)。
  • 想弄清楚為什么這樣可行, 可以考慮以下兩個(gè)鏈表: A={1,3,5,7,9,11} 和 B={2,4,9,11},相交于結(jié)點(diǎn) 9。 由于 B.length (=4) < A.length (=6),pB 比 pA 少經(jīng)過(guò) 2 個(gè)結(jié)點(diǎn),會(huì)先到達(dá)尾部。將 pB 重定向到 A 的頭結(jié)點(diǎn),pApA 重定向到 B 的頭結(jié)點(diǎn)后,pB 要比 pA 多走 2 個(gè)結(jié)點(diǎn)。因此,它們會(huì)同時(shí)到達(dá)交點(diǎn)。
  • 如果兩個(gè)鏈表存在相交,它們末尾的結(jié)點(diǎn)必然相同。因此當(dāng) pA/pB 到達(dá)鏈表結(jié)尾時(shí),記錄下鏈表 A/B 對(duì)應(yīng)的元素。若最后元素不相同,則兩個(gè)鏈表不相交。
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        /// 雙指針
        ListNode pA = headA, pB = headB;
        while(pA != pB) {
            pA = pA == null ? headB : pA.next;
            pB = pB == null ? headA : pB.next;
        }
        return pA;
    }
}
五、刪除鏈表中等于給定值 val 的所有節(jié)點(diǎn)。

示例:
輸入: 1->2->6->3->4->5->6, val = 6
輸出: 1->2->3->4->5
這里需要注意的是:如果刪除的節(jié)點(diǎn)位于鏈表的頭部時(shí) ,我們使用哨兵節(jié)點(diǎn)來(lái)解決該問(wèn)題

  • 初始化哨兵節(jié)點(diǎn)為 ListNode(0) 且設(shè)置 sentinel.next = head。
  • 初始化兩個(gè)指針 curr 和 prev 指向當(dāng)前節(jié)點(diǎn)和前繼節(jié)點(diǎn)。
  • 當(dāng) curr != null:
  • 比較當(dāng)前節(jié)點(diǎn)和要?jiǎng)h除的節(jié)點(diǎn):
  • 若當(dāng)前節(jié)點(diǎn)就是要?jiǎng)h除的節(jié)點(diǎn):則 prev.next = curr.next。
  • 否則設(shè) prve = curr。
  • 遍歷下一個(gè)元素:curr = curr.next。
  • 返回 sentinel.next。
    :哨兵節(jié)點(diǎn)廣泛應(yīng)用于樹(shù)和鏈表中,如偽頭、偽尾、標(biāo)記等,它們是純功能的,通常不保存任何數(shù)據(jù),其主要目的是使鏈表標(biāo)準(zhǔn)化,如使鏈表永不為空、永不無(wú)頭、簡(jiǎn)化插入和刪除。
class Solution {
    public ListNode removeElements(ListNode head, int val) {
        ListNode sentinel = new ListNode(0); // 哨兵節(jié)點(diǎn),虛擬增加一個(gè)頭結(jié)點(diǎn)
        sentinel.next = head;
        
        ListNode prev = sentinel, curr = head;
        while (curr != null) {
            if (curr.val == val) {
                prev.next = curr.next;
            } else {
                prev = curr;
            } 
            curr = curr.next;
        }
        return sentinel.next;
    }
}
六、翻轉(zhuǎn)鏈表
class Solution {
    public ListNode reverseList(ListNode head) {
        // 1、 迭代
        // ListNode preHead = null;
        // ListNode prev = head;
        // while(prev != null) {
        //     ListNode temp = prev.next;
        //     prev.next = preHead;
        //     preHead = prev;
        //     prev = temp;
        // }
        // return preHead;

        /// 2、 遞歸
        if (head == null || head.next == null) return head;
        ListNode temp = reverseList(head.next);
        head.next.next = head;
        head.next = null;
        return temp;
    }
}
七、請(qǐng)判斷一個(gè)鏈表是否為回文鏈表。

先翻轉(zhuǎn)鏈表 再比較

class Solution {
    public boolean isPalindrome(ListNode head) {
        if (head == null || head.next == null) {
            return true;
        }
        ListNode slow = head, fast = head, prev = null, prepre = null;
        while(fast != null && fast.next != null) {
            /// 移動(dòng)指針
            prev = slow;
            slow = slow.next;
            fast = fast.next.next;
            /// 翻轉(zhuǎn)前半部鏈表
            prev.next = prepre;
            prepre = prev;

        }
        if(fast != null) { slow = slow.next;} // 鏈表為奇數(shù),需要去除中間節(jié)點(diǎn)
        // 遍歷 對(duì)比
        while (prev != null && slow != null) {
            if (slow.val != prev.val) {
                return false;
            }
            slow = slow.next;
            prev = prev.next;
        }
        return true;
    }
}
八、請(qǐng)編寫(xiě)一個(gè)函數(shù),使其可以刪除某個(gè)鏈表中給定的(非末尾)節(jié)點(diǎn)

從鏈表里刪除一個(gè)節(jié)點(diǎn) node 的最常見(jiàn)方法是修改之前節(jié)點(diǎn)的 next 指針,使其指向之后的節(jié)點(diǎn)。
在這里我們無(wú)法訪(fǎng)問(wèn)我們想要?jiǎng)h除的節(jié)點(diǎn) 之前 的節(jié)點(diǎn),我們始終不能修改該節(jié)點(diǎn)的 next 指針。但是我們可以將想要?jiǎng)h除的節(jié)點(diǎn)的值替換為它后面節(jié)點(diǎn)中的值,然后刪除它之后的節(jié)點(diǎn)。
因?yàn)槲覀冎酪獎(jiǎng)h除的節(jié)點(diǎn)不是列表的末尾,所以我們可以保證這種方法是可行的。

class Solution {
    public void deleteNode(ListNode node) {
        node.val = node.next.val;
        node.next = node.next.next;
    }
}
九、給定一個(gè)帶有頭結(jié)點(diǎn) head 的非空單鏈表,返回鏈表的中間結(jié)點(diǎn)。如果有兩個(gè)中間結(jié)點(diǎn),則返回第二個(gè)中間結(jié)點(diǎn)。

使用雙指針,通過(guò)快慢指針找到中間點(diǎn)

class Solution {
    public ListNode middleNode(ListNode head) {
        ListNode slow = head;
        ListNode fast = head;
        // fast != null && fast.next != null 用于找到第二個(gè)中間節(jié)點(diǎn)
        // fast.next != null && fast.next.next != null 用于找到第一個(gè)中間節(jié)點(diǎn)
        while( fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }
        return slow;
    }
}
十、給你一個(gè)單鏈表的引用結(jié)點(diǎn) head。鏈表中每個(gè)結(jié)點(diǎn)的值不是 0 就是 1。已知此鏈表是一個(gè)整數(shù)數(shù)字的二進(jìn)制表示形式。

請(qǐng)你返回該鏈表所表示數(shù)字的 十進(jìn)制值 。

class Solution {
    public int getDecimalValue(ListNode head) {
        int sum = 0;
        while (head != null) {
            sum = sum * 2 + head.val;
            head = head.next;
        }
        return sum;
    }
}

注: 以上鏈表問(wèn)題皆來(lái)源于https://leetcode-cn.com/problems/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 搞懂單鏈表常見(jiàn)面試題 Hello 繼上次的 搞懂基本排序算法,這個(gè)一星期,我總結(jié)了,我所學(xué)習(xí)和思考的單鏈表基礎(chǔ)知識(shí)...
    醒著的碼者閱讀 4,739評(píng)論 1 45
  • LeetCode-鏈表 鏈表(Linked List)是一種常見(jiàn)的基礎(chǔ)數(shù)據(jù)結(jié)構(gòu),是一種線(xiàn)性表,但是并不會(huì)按線(xiàn)性的順...
    raincoffee閱讀 1,337評(píng)論 0 6
  • 轉(zhuǎn)載請(qǐng)注明出處:http://www.itdecent.cn/p/c65d9d753c31 在上一篇博客《數(shù)據(jù)結(jié)構(gòu)...
    Alent閱讀 3,604評(píng)論 4 74
  • (一)LeetCode206.反轉(zhuǎn)鏈表 題目描述: 反轉(zhuǎn)一個(gè)單鏈表。 代碼實(shí)現(xiàn) (二)LeetCode160. 相...
    Jarily閱讀 1,486評(píng)論 0 5
  • 上帝之眼,代表著上帝監(jiān)視人類(lèi)的法眼, 上帝之手,代表著上帝監(jiān)管人類(lèi)的規(guī)矩, 擁有了上帝之眼和上帝之手, 是否就可以...
    陟卓閱讀 522評(píng)論 2 15

友情鏈接更多精彩內(nèi)容