https://leetcode.com/problems/find-k-th-smallest-pair-distance/
この実装はパスしなかった。
組み合わせをすべて列挙して、 {距離: ペアの数}
で HashMap を作る。k
を ペアの回数
の少ない順に引いていき、マイナスになったときの 距離
を答えとする。
これだと
use std::collections::HashMap;
impl Solution {
pub fn smallest_distance_pair(nums: Vec<i32>, mut k: i32) -> i32 {
let mut distance_count: HashMap<i32, i32> = HashMap::new();
for i in 0..nums.len() {
for j in i + 1..nums.len() {
let distance = i32::abs(nums[i] - nums[j]);
distance_count
.entry(distance)
.and_modify(|v| *v += 1)
.or_insert(1);
}
}
let mut distance_count_vec = distance_count.iter().collect::<Vec<(&i32, &i32)>>();
distance_count_vec.sort();
*distance_count_vec
.iter()
.skip_while(|(_, v)| {
k -= **v;
k > 0
})
.next()
.unwrap()
.0
}
}
公式の解法読んだけど説明がまじで意味不明過ぎて数日ほど理解できなかった。 ユーザーが投稿した解法が分かりやすかった(そして公式の説明は未だ意味不明)ので、以下にまとめてみる。 https://leetcode.com/problems/find-k-th-smallest-pair-distance/solutions/109082/approach-the-problem-using-the-trial-and-error-algorithm/
問題文で求められている、
return the kth smallest distance among all the pairs nums[i] and nums[j] where 0 <= i < j < nums.length.
とはどういうことかと言うと以下の通りである。
0 以上の整数を要素に持つある配列 arr
のうち、2 要素の組み合わせがなす距離の範囲は [0, (max(arr) - min(arr))]
である。たとえば、
という配列があった場合、
となる。この 0 から 6 の距離を取る組み合わせを小さい順に並べると、
[[1, 1], [5, 5], [4, 5], [3, 4], [4, 5], [5, 7], [1, 3], [1, 3], [3, 5], [3, 5], [5, 7], [1, 4], [4, 7], [1, 4], [1, 5], [1, 5], [3, 7], [1, 5], [1, 5], [1, 7], [1, 7]]
距離ごとにグループ分けすると以下の通り。
0=>[[1, 1], [5, 5]],
1=>[[3, 4], [4, 5], [4, 5]],
2=>[[1, 3], [1, 3], [3, 5], [3, 5], [5, 7], [5, 7]],
3=>[[1, 4], [1, 4], [4, 7]],
4=>[[1, 5], [1, 5], [1, 5], [1, 5], [3, 7]],
6=>[[1, 7], [1, 7]]
たとえば、 the kth smallest distance
の候補は 0,1,2,3,4,6 のどれかしかないんじゃないの?と思うけど、そうではなく、k 番目の組み合わせがなす距離のことを指すらしい。find the kth smallest pair and return its distance
の方がまだ分かりやすい。
とにかく、k 番目の組み合わせがなす距離を返すには、
(0 ~ m 番目までの組み合わせ総数) >= k
となるような距離 m の最小値が分かれば OK。コードに落とし込むべきことは以下の 2 つ。
- 取りうる距離の範囲を求める
- 配列 nums をソートする
- 最小値を 0、最大値を
max = (nums[n-1] - nums[0])
とする
- 0 ~ max を m の探索範囲とし、二分探索で絞っていく
- 0 ~ m 番目までの組み合わせ総数の計算を行う
- 組み合わせ総数 < k なら右半分に探索範囲を絞る
- 組み合わせ総数 >= k なら左半分に探索範囲を絞る
impl Solution {
pub fn smallest_distance_pair(mut nums: Vec<i32>, mut k: i32) -> i32 {
nums.sort();
let n = nums.len();
let mut l = 0;
let mut r = nums[n - 1] - nums[0];
while l < r {
let mut count = 0;
let m = l + (r - l) / 2;
for i in 0..n {
let mut j = i;
while j < n && nums[j] - nums[i] <= m {
j += 1;
}
count += (j as i32 - 1) - i as i32;
}
if count < k {
l = m + 1;
} else {
r = m;
}
}
l
}
}