當Rust遇上LeetCode #23. 合并k個排序鏈表 [困難]

2020/2/17

題目描述

  • 合并 k 個排序鏈表,返回合并后的排序鏈表。

示例

輸入:
[
  1->4->5,
  1->3->4,
  2->6
]
輸出: 1->1->2->3->4->4->5->6

相關標簽


列表
分治算法

解題思路

  • 逐一比較法
    算法
    比較 k 個節點(每個鏈表的首節點),獲得最小值的節點。
    將選中的節點接在最終有序鏈表的后面。
    復雜度分析
    時間復雜度
    O(kN) ,其中 k 是鏈表的數目。幾乎最終有序鏈表中每個節點的時間開銷都為 O(k) ( k-1 次比較)。總共有 N 個節點在最后的鏈表中。
    空間復雜度
    O(n) 。創建一個新的鏈表空間開銷為 O(n) 。O(1) 。重復利用原來的鏈表節點,每次選擇節點時將它直接接在最后返回的鏈表后面,而不是創建一個新的節點。

  • 優先隊列法
    算法
    幾乎與上述方法一樣,除了將 比較環節優先隊列 進行了優化。
    復雜度分析
    時間復雜度
    O(Nlogk) ,其中 k 是鏈表的數目。彈出操作時,比較操作的代價會被優化到 O(logk) 。同時,找到最小值節點的時間開銷僅僅為 O(1)。最后的鏈表中總共有 N 個節點。
    空間復雜度:同上。

  • 分治法
    算法
    分治法將k個鏈表進行逐對的合并,每一輪合并相比上一輪合并的次數減半,總計共進行log2(k)輪。重復這一過程,最終就可以得到需要的唯一有序鏈表。

    合并過程

    復雜度分析
    時間復雜度: O(Nlogk),n為兩個鏈表中的總節點數
    空間復雜度:O(1)

源代碼

1. 逐一比較法

// Definition for singly-linked list.
// #[derive(PartialEq, Eq, Clone, Debug)]
// pub struct ListNode {
//   pub val: i32,
//   pub next: Option<Box<ListNode>>
// }
// 
// impl ListNode {
//   #[inline]
//   fn new(val: i32) -> Self {
//     ListNode {
//       next: None,
//       val
//     }
//   }
// }
impl Solution {
    pub fn merge_k_lists(lists: Vec<Option<Box<ListNode>>>) -> Option<Box<ListNode>> {
        let mut val = 9999;
        let len: usize = lists.len();
        let mut ans: Option<Box<ListNode>> = None;
        let mut ans_p = &mut ans;
        let mut ps: Vec<&Option<Box<ListNode>>> = vec![];
        for list_node in lists.iter() {
            ps.push(&list_node);
        }
        let mut count = 0;
        let mut min_val = 9999;
        let mut min_index = 0;
        loop {
            for i in 0..len{
                if let Some(node) = ps[i]{
                    // println!("node_val: {}, min_val: {}", node.val, min_val);
                    if(min_val > node.val){
                        min_val = node.val;
                        min_index = i;
                    }                    
                    count += 1;
                }
                // println!("{}", count);
            }
            if(count == 0) {
                break;
            }else{
                // println!("{}", min_val);
                *ans_p = Some(Box::new(ListNode::new(min_val)));
                if let Some(node) = ans_p{
                    ans_p = &mut node.next;
                }
                if let Some(node) = ps[min_index]{
                    ps[min_index] = &node.next;
                }                
                count = 0;
                min_val = 9999;
            }
        };
        ans
    }
}
  • 執行用時 : 288 ms, 在所有 Rust 提交中擊敗了11.43%的用戶
  • 內存消耗 : 3.1 MB, 在所有 Rust 提交中擊敗了53.57%的用戶

2. 優先隊列優化

Rust標準庫中并沒有包含優先隊列,需要自己實現比較麻煩

use std::collections::{BTreeSet, BTreeMap, HashSet, HashMap, BinaryHeap};
use std::{i32, i64, u32, u64};
use std::cmp::{Reverse, Ordering};

struct KVPair {
    value: i32,
    from_list: usize
}

impl PartialEq for KVPair {
    fn eq(&self, other: &Self) -> bool {
        self.value == other.value
    }

    fn ne(&self, other: &Self) -> bool {
        self.value != other.value
    }
}

impl PartialOrd for KVPair {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.value.cmp(&other.value))
    }

    fn lt(&self, other: &Self) -> bool {
        self.value < other.value
    }

    fn le(&self, other: &Self) -> bool {
        self.value <= other.value
    }

    fn gt(&self, other: &Self) -> bool {
        self.value > other.value
    }

    fn ge(&self, other: &Self) -> bool {
        self.value >= other.value
    }
}

impl Eq for KVPair {}

impl Ord for KVPair {
    fn cmp(&self, other: &Self) -> Ordering {
        self.partial_cmp(other).unwrap()
    }
}

impl KVPair {
    pub fn new(value: i32, from_list: usize) -> Self {
        KVPair { value, from_list }
    }
}

impl Solution {
    pub fn merge_k_lists(lists: Vec<Option<Box<ListNode>>>) -> Option<Box<ListNode>> {
        let mut heap = BinaryHeap::new();
        let mut heads = Vec::new();
        for list in lists.iter() {
            heads.push(list);
        }

        for (i, head) in heads.iter_mut().enumerate() {
            if let Some(node) = head {
                heap.push(Reverse(KVPair::new(node.val, i)));
                *head = &node.next;
            }
        }

        let mut merged_list: Option<Box<ListNode>> = None;
        let mut list_tail = &mut merged_list;

        while let Some(kv_pair) = heap.pop() {
            let new_node = Box::new(ListNode::new(kv_pair.0.value));
            if let Some(node) = heads[kv_pair.0.from_list] {
                heap.push(Reverse(KVPair::new(node.val, kv_pair.0.from_list)));
                heads[kv_pair.0.from_list] = &node.next;
            }
            if let Some(tail) = list_tail {
                tail.next = Some(new_node);
                list_tail = &mut tail.next;
            } else {
                *list_tail = Some(new_node);
            }
        }

        merged_list
    }
}
  • 執行用時 : 4 ms, 在所有 Rust 提交中擊敗了100.00%的用戶
  • 內存消耗 : 3.3 MB, 在所有 Rust 提交中擊敗了17.86%的用戶

2. 分治法

impl Solution {
    //分治法
    pub fn merge_k_lists(lists: Vec<Option<Box<ListNode>>>) -> Option<Box<ListNode>> {
        let (len, mut interval) = (lists.len(), 1);
        let mut heads = lists;

        if len == 0 {
            return None;
        }

        while interval < len {
            let mut i = 0;
            while i < len-interval {
                heads[i] = Self::merge_two_lists(&mut heads[i] as *mut Option<Box<ListNode>>,
                                                 &mut heads[i+interval] as *mut Option<Box<ListNode>>);
                i += interval*2;
            }
            interval *= 2;
        }

        heads[0].clone()
    }
    
   pub fn merge_two_lists(l1: *mut Option<Box<ListNode>>, l2: *mut Option<Box<ListNode>>) -> Option<Box<ListNode>> {        
        let mut head = Some(Box::new(ListNode::new(0)));
        let (mut p1, mut p2, mut pr) = (l1, l2, &mut head as *mut Option<Box<ListNode>>);
        let (mut l1, mut l2) = (None, None);

        unsafe {
            let node_val = |x: *mut Option<Box<ListNode>>| {
                if (*x) == None {
                    None
                }else {
                    Some((*x).as_ref().unwrap().val)
                }
            };

            let node_next = |x: *mut Option<Box<ListNode>>| {
                if (*x) == None {
                    x
                }else {
                    &mut (*x).as_mut().unwrap().next as *mut Option<Box<ListNode>>
                }
            };

            loop {
                match (node_val(p1), node_val(p2)) {
                    (Some(v1), Some(v2)) => {
                       if v1 < v2 {
                            if (*node_next(pr)) != None && node_next(pr) == p2 {
                                l2 = (*node_next(pr)).take();
                                p2 = &mut l2 as *mut Option<Box<ListNode>>;                                
                            }         
                            (*node_next(pr)) = (*p1).take();
                            pr = node_next(pr);
                            p1 = node_next(pr);
                        }else {
                            if (*node_next(pr)) != None && node_next(pr) == p1 {
                                
                                 = (*node_next(pr)).take();
                                p1 = &mut l1 as *mut Option<Box<ListNode>>;                                
                            }
                            (*node_next(pr)) = (*p2).take();
                            pr = node_next(pr);
                            p2 = node_next(pr);
                        };
                    },
                    (Some(_), None) => {
                        (*pr).as_mut().unwrap().next = (*p1).take();
                        break;
                    },
                    (None, Some(_)) => {
                        (*pr).as_mut().unwrap().next = (*p2).take();
                        break;
                    },
                    (None, None) => {
                        return None;
                    },
                }
            }
        }
        head.unwrap().next
    }
}
  • 執行用時 : 8 ms, 在所有 Rust 提交中擊敗了84.62%的用戶
  • 內存消耗 :3 MB, 在所有 Rust 提交中擊敗了72.00%的用戶

總結

分治法中,合并兩個鏈表的部分比較頭疼,雖然在“21. 合并兩個有序鏈表”中嘗試的“迭代法”和“取鏈表頭組合法”都是0ms通過,但放在這道題中,前者要比后者慢100倍之多。猜測是迭代法的實現存在大量的clone,拖慢了時間。若要在擺脫clone,轉為調用指針的方法,那就要通過rc智能指針或者unsafe的裸指針,前者與所給的數據結構不匹配,后者打算之后進一步嘗試。遞歸法和取鏈表頭組合差不多時間。目前最快的還是自己構造優先隊列的方法,能達到4ms。
另外,本題中使用了迭代而非遞歸的方法完成歸并,可以留心一下寫法。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,362評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,013評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,346評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,421評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,146評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,534評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,585評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,767評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,318評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,074評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,258評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,828評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,486評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,916評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,156評論 1 290
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,993評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,234評論 2 375

推薦閱讀更多精彩內容