在鏈表上實現 Partition 以及荷蘭國旗問題

在鏈表上實現 Partition 以及荷蘭國旗問題

作者:Grey

原文地址:

博客園:在鏈表上實現 Partition 以及荷蘭國旗問題

CSDN:在鏈表上實現 Partition 以及荷蘭國旗問題

題目描述

給你一個鏈表的頭節點 head 和一個特定值 x ,請你對鏈表進行分隔,使得所有 小於 x 的節點都出現在 大於或等於 x 的節點之前。

你應當 保留 兩個分區中每個節點的初始相對位置。

題目鏈接見:LeetCode 86. Partition List

主要思路

本題可以借鑑數組的 Partition 操作,參考:荷蘭國旗問題與快速排序算法

Partition 操作就是荷蘭國旗的一種特殊情況而已。

我們可以把本題的難度稍微升級一下:如何在鏈表上實現荷蘭國旗問題?

第一種解法就是將鏈表先轉換成數組,然後在數組上實現荷蘭國旗問題,最後將數組還原爲鏈表並返回,該方法的時間複雜度是O(N),空間複雜度是O(N),不是最優解。

第二種解法是用有限幾個變量來實現,在同樣O(N)的時間複雜度的情況下,空間複雜度可以做到O(1),設置如下幾個變量

ListNode sH = null; // 小於區域的頭結點
ListNode sT = null; // 小於區域的尾結點
ListNode eH = null; // 等於區域的頭結點
ListNode eT = null; // 等於區域的尾結點
ListNode mH = null; // 大於區域的頭結點
ListNode mT = null; // 大於區域的尾結點
ListNode next; // 記錄遍歷到的結點的下一個結點

接下來開始遍歷鏈表,進行主流程處理,僞代碼如下

while (head != null) {
    next = head.next;
    // 如果head.val < pivot,則通過sH,sT構造小於區域的鏈表
    // 如果head.val == pivot,則通過eH,eT構造小於區域的鏈表
    // 如果head.val > pivot,則通過mH,mT構造小於區域的鏈表
    head = next;
}
// 把三個區域的鏈表串聯起來即可。

構造每個區域的鏈表的時候,還要考慮鏈表是第一次被構造還是已經構造了,以小於區域的鏈表爲例,如果是第一次構造,則sH == null,此時需要把sHsT同時初始化:

if (sH == null) {
    sH = head;
    sT = head;
} 

如果不是第一次構造,則

sT.next = head;
sT = head;

開始區域的尾指針直接指向當前結點,然後把尾指針設置未當前結點即可。

其他兩個區域的鏈表處理方式同理。

最後需要把三個區域的鏈表連接起來:

        // 小於區域的尾巴,連等於區域的頭,等於區域的尾巴連大於區域的頭
        if (sT != null) { // 如果有小於區域
            sT.next = eH;
            eT = eT == null ? sT : eT; // 下一步,誰去連大於區域的頭,誰就變成eT
        }
        // all reconnect
        if (eT != null) { // 如果小於區域和等於區域,不是都沒有
            eT.next = mH;
        }
        // 如果小於區域有,小於區域的頭就是最終鏈表的頭
        // 如果小於區域沒有,等於區域的頭有,則等於區域的頭就是最終鏈表的頭
        // 如果小於和等於區域都沒有,則大於區域的頭就是最終鏈表的頭
        return sH != null ? sH : (eH != null ? eH : mH);

完整代碼見

public class Code_PartitionList {

    public static class ListNode {
        public int val;
        public ListNode next;

        public ListNode(int data) {
            this.val = data;
        }
    }


    public static ListNode listPartition2(ListNode head, int pivot) {
        ListNode sH = null; // small head
        ListNode sT = null; // small tail
        ListNode eH = null; // equal head
        ListNode eT = null; // equal tail
        ListNode mH = null; // big head
        ListNode mT = null; // big tail
        ListNode next; // save next node
        // every node distributed to three lists
        while (head != null) {
            next = head.next;
            head.next = null;
            if (head.val < pivot) {
                if (sH == null) {
                    sH = head;
                    sT = head;
                } else {
                    sT.next = head;
                    sT = head;
                }
            } else if (head.val == pivot) {
                if (eH == null) {
                    eH = head;
                    eT = head;
                } else {
                    eT.next = head;
                    eT = head;
                }
            } else {
                if (mH == null) {
                    mH = head;
                    mT = head;
                } else {
                    mT.next = head;
                    mT = head;
                }
            }
            head = next;
        }
        // 小於區域的尾巴,連等於區域的頭,等於區域的尾巴連大於區域的頭
        if (sT != null) { // 如果有小於區域
            sT.next = eH;
            eT = eT == null ? sT : eT; // 下一步,誰去連大於區域的頭,誰就變成eT
        }
        // all reconnect
        if (eT != null) { // 如果小於區域和等於區域,不是都沒有
            eT.next = mH;
        }
        // 如果小於區域有,小於區域的頭就是最終鏈表的頭
        // 如果小於區域沒有,等於區域的頭有,則等於區域的頭就是最終鏈表的頭
        // 如果小於和等於區域都沒有,則大於區域的頭就是最終鏈表的頭
        return sH != null ? sH : (eH != null ? eH : mH);
    }
}

解決了鏈表的荷蘭國旗問題,那麼原題中的鏈表 Partition 問題,就迎刃而解了。

由於是 Partition,所以不存在等於區域,只需要考慮小於區域和大於區域,只需要設置如下幾個變量即可。

ListNode sH = null; // 小於區域的頭
ListNode sT = null; // 小於區域的尾
ListNode mH = null; // 大於區域的頭
ListNode mT = null; // 大於區域的尾
ListNode cur = head; // 當前遍歷到的結點

接下來開始遍歷鏈表,進行主流程處理,僞代碼如下

while (cur != null) {
    // 如果head.val < pivot,則通過sH,sT構造小於區域的鏈表
    // 如果head.val > pivot,則通過mH,mT構造小於區域的鏈表
    cur = cur.next;
}
// 把兩個區域的鏈表串聯起來即可。

構造每個區域的鏈表的時候和上述荷蘭國旗問題一樣。

最後需要把兩個區域的鏈表連接起來:


        if (mT != null) {
            // 大於區域的尾置空
            mT.next = null;
        }
        
        if (sT != null) {
            // 小於區域的尾置空
            sT.next = null;
        }
        // 經過上述操作,兩個鏈表斷開連接
        if (sH == null) {
            // 小於區域的頭爲空,說明只有大於區域
            return mH;
        }
        // 小於區域的頭不爲空,小於區域的尾一定也不爲空,直接把小於區域的尾連上大於區域的頭即可。
        sT.next = mH;
        return sH;

完整代碼見

class Solution {
    public static ListNode partition(ListNode head, int x) {
        ListNode sH = null;
        ListNode sT = null;
        ListNode mH = null;
        ListNode mT = null;
        ListNode cur = head;
        while (cur != null) {
            if (cur.val < x) {
                if (sH == null) {
                    sH = cur;
                } else {
                    sT.next = cur;
                }
                sT = cur;
            } else {
                // cur.val >= x
                // 都放到大於等於區域
                if (mH == null) {
                    mH = cur;
                } else {
                    mT.next = cur;
                }
                mT = cur;
            }
            cur = cur.next;
        }
        if (mT != null) {
            mT.next = null;
        }
        if (sT != null) {
            sT.next = null;
        }
        if (sH == null) {
            return mH;
        }
        sT.next = mH;
        return sH;
    }
}

更多

算法和數據結構筆記

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章