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。
另外,本題中使用了迭代而非遞歸的方法完成歸并,可以留心一下寫法。