|
| 1 | +// 2463. Minimum Total Distance Traveled |
| 2 | +// 🔴 Hard |
| 3 | +// |
| 4 | +// https://leetcode.com/problems/minimum-total-distance-traveled/ |
| 5 | +// |
| 6 | +// Tags: Array - Dynamic Programming - Sorting |
| 7 | + |
| 8 | +use std::{cmp::Reverse, collections::BinaryHeap}; |
| 9 | + |
| 10 | +struct Solution; |
| 11 | +impl Solution { |
| 12 | + /// This solution does not work, I assumed that greedily choosing the closest factory would |
| 13 | + /// result in the most optimal result, but it is not the case, there is one test case that |
| 14 | + /// shows this. |
| 15 | + /// |
| 16 | + /// Time complexity: O(m*log(m)+m*log(n)) - First we sort the robots, then we process them in |
| 17 | + /// order, for each, we pick the closets factory that can fix robots in O(log(n)) |
| 18 | + /// Space complexity: O(m) - The heaps. |
| 19 | + #[allow(dead_code)] |
| 20 | + pub fn minimum_total_distance_greedy_heaps(mut robot: Vec<i32>, factory: Vec<Vec<i32>>) -> i64 { |
| 21 | + robot.sort_unstable(); |
| 22 | + // Two heaps to find the closest factory to any robot. |
| 23 | + let mut left = BinaryHeap::<(i32, i32)>::new(); |
| 24 | + let mut right = factory |
| 25 | + .iter() |
| 26 | + .map(|v| (Reverse((v[0], v[1])))) |
| 27 | + .collect::<BinaryHeap<_>>(); |
| 28 | + let mut res = 0; |
| 29 | + for r in robot { |
| 30 | + while let Some(&Reverse((position, _capacity))) = right.peek() { |
| 31 | + // Move factories to the left/same spot to the min heap. |
| 32 | + if position > r { |
| 33 | + break; |
| 34 | + } |
| 35 | + left.push(right.pop().unwrap().0); |
| 36 | + } |
| 37 | + // Find out which factory is closer. |
| 38 | + let go_right = match (left.peek(), right.peek()) { |
| 39 | + (None, Some(_)) => true, |
| 40 | + (Some(_), None) => false, |
| 41 | + (Some(&(left_pos, _)), Some(&Reverse((right_pos, _)))) => { |
| 42 | + // If the distance to the right factory is closer. |
| 43 | + (r - left_pos).abs() > (right_pos - r).abs() |
| 44 | + } |
| 45 | + (None, None) => unreachable!("The problem guarantees enough factory capacity"), |
| 46 | + }; |
| 47 | + if go_right { |
| 48 | + let (pos, capacity) = right.pop().unwrap().0; |
| 49 | + res += (pos - r).abs() as i64; |
| 50 | + // println!("Robot {} going right to {}. Total {}", r, pos, res); |
| 51 | + if capacity > 1 { |
| 52 | + right.push(Reverse((pos, capacity - 1))); |
| 53 | + } |
| 54 | + } else { |
| 55 | + let (pos, capacity) = left.pop().unwrap(); |
| 56 | + res += (r - pos).abs() as i64; |
| 57 | + // println!("Robot {} going left to {}. Total {}", r, pos, res); |
| 58 | + if capacity > 1 { |
| 59 | + left.push((pos, capacity - 1)); |
| 60 | + } |
| 61 | + } |
| 62 | + } |
| 63 | + res |
| 64 | + } |
| 65 | + |
| 66 | + /// Use dynamic programming. See the explanation on the problem editorial. |
| 67 | + /// |
| 68 | + /// Time complexity: O(m*n*k) - The nested loops. |
| 69 | + /// Space complexity: O(m) - The dp vector. |
| 70 | + /// |
| 71 | + /// Runtime 4 ms Beats 100% |
| 72 | + /// Memory 2.20 MB Beats 100% |
| 73 | + pub fn minimum_total_distance(mut robot: Vec<i32>, mut factory: Vec<Vec<i32>>) -> i64 { |
| 74 | + robot.sort_unstable(); |
| 75 | + factory.sort_unstable(); |
| 76 | + let (m, n) = (robot.len(), factory.len()); |
| 77 | + let mut dp = vec![i64::MAX; m + 1]; |
| 78 | + dp[m] = 0; |
| 79 | + for fidx in (0..n).rev() { |
| 80 | + for ridx in 0..m { |
| 81 | + let mut cur = 0; |
| 82 | + // The number of robots that this factory can fix min the number of robots needing |
| 83 | + // fixing still. |
| 84 | + for k in 1..=(factory[fidx][1] as usize).min(m - ridx) as usize { |
| 85 | + cur += (robot[ridx + k - 1] - factory[fidx][0]).abs() as i64; |
| 86 | + dp[ridx] = dp[ridx].min(if dp[ridx + k] == i64::MAX { |
| 87 | + i64::MAX |
| 88 | + } else { |
| 89 | + dp[ridx + k] + cur |
| 90 | + }); |
| 91 | + } |
| 92 | + // println!("{:?}", dp); |
| 93 | + } |
| 94 | + } |
| 95 | + dp[0] |
| 96 | + } |
| 97 | +} |
| 98 | + |
| 99 | +// Tests. |
| 100 | +fn main() { |
| 101 | + let tests = [ |
| 102 | + (vec![0, 4, 6], vec![vec![2, 2], vec![6, 2]], 4), |
| 103 | + (vec![1, -1], vec![vec![-2, 1], vec![2, 1]], 2), |
| 104 | + // This test fails on the greedy/heap solution, it is better for 9 to go to 7 even though |
| 105 | + // it is further. |
| 106 | + ( |
| 107 | + vec![9, 11, 99, 101], |
| 108 | + vec![ |
| 109 | + vec![10, 1], |
| 110 | + vec![7, 1], |
| 111 | + vec![14, 1], |
| 112 | + vec![100, 1], |
| 113 | + vec![96, 1], |
| 114 | + vec![103, 1], |
| 115 | + ], |
| 116 | + 6, |
| 117 | + ), |
| 118 | + ]; |
| 119 | + println!("\n\x1b[92m» Running {} tests...\x1b[0m", tests.len()); |
| 120 | + let mut success = 0; |
| 121 | + for (i, t) in tests.iter().enumerate() { |
| 122 | + let res = Solution::minimum_total_distance(t.0.clone(), t.1.clone()); |
| 123 | + if res == t.2 { |
| 124 | + success += 1; |
| 125 | + println!("\x1b[92m✔\x1b[95m Test {} passed!\x1b[0m", i); |
| 126 | + } else { |
| 127 | + println!( |
| 128 | + "\x1b[31mx\x1b[95m Test {} failed expected: {:?} but got {}!!\x1b[0m", |
| 129 | + i, t.2, res |
| 130 | + ); |
| 131 | + } |
| 132 | + } |
| 133 | + println!(); |
| 134 | + if success == tests.len() { |
| 135 | + println!("\x1b[30;42m✔ All tests passed!\x1b[0m") |
| 136 | + } else if success == 0 { |
| 137 | + println!("\x1b[31mx \x1b[41;37mAll tests failed!\x1b[0m") |
| 138 | + } else { |
| 139 | + println!( |
| 140 | + "\x1b[31mx\x1b[95m {} tests failed!\x1b[0m", |
| 141 | + tests.len() - success |
| 142 | + ) |
| 143 | + } |
| 144 | +} |
0 commit comments