leetcode

Log | Files | Refs | README

two_sum.rs (8984B)


      1 //! Given an array of integers nums and an integer target, return indices
      2 //! of the two numbers such that they add up to target.
      3 //!
      4 //! You may assume that each input would have exactly one solution, and
      5 //! you may not use the same element twice.
      6 //!
      7 //! You can return the answer in any order.
      8 
      9 // First try - brute force approach without optimization
     10 // 101ms, 2.37MB
     11 pub fn two_sum_1(nums: Vec<i32>, target: i32) -> Vec<i32> {
     12     let mut result = std::collections::HashSet::<i32>::new();
     13 
     14     nums.iter().enumerate().for_each(|(idx, x)| {
     15         let pair = nums
     16             .iter()
     17             .enumerate()
     18             .find(|(idx2, y)| *y + x == target && &idx != idx2);
     19         if let Some((idx2, _)) = pair {
     20             result.insert(idx as i32);
     21             result.insert(idx2 as i32);
     22         }
     23     });
     24 
     25     result.into_iter().collect()
     26 }
     27 
     28 // 56ms, 2.23MB
     29 pub fn two_sum_2(nums: Vec<i32>, target: i32) -> Vec<i32> {
     30     let mut result = std::collections::HashSet::<i32>::new();
     31 
     32     for (idx, num) in nums.iter().enumerate() {
     33         let pair = target - num;
     34         let idx2 = nums
     35             .iter()
     36             .enumerate()
     37             .position(|(i, x)| x == &pair && i != idx);
     38         if let Some(idx2) = idx2 {
     39             result.insert(idx as i32);
     40             result.insert(idx2 as i32);
     41             return result.into_iter().collect();
     42         }
     43     }
     44     result.into_iter().collect()
     45 }
     46 
     47 // 86ms, 2.28MB
     48 // is position is faster than rposition?
     49 pub fn two_sum_3(nums: Vec<i32>, target: i32) -> Vec<i32> {
     50     for (i, x) in nums.iter().enumerate() {
     51         let idx = nums
     52             .iter()
     53             .enumerate()
     54             .rposition(|(i2, y)| i2 != i && *y == target - x);
     55         if let Some(idx) = idx {
     56             return Vec::from([i as i32, idx as i32]);
     57         }
     58     }
     59     Vec::new()
     60 }
     61 
     62 // 27ms 2.22MB
     63 pub fn two_sum_4(nums: Vec<i32>, target: i32) -> Vec<i32> {
     64     for (i, x) in nums.iter().enumerate() {
     65         let i2 = nums.iter().position(|y| y == &(target - x));
     66         if i2.is_some_and(|z| z != i) {
     67             return vec![i as i32, i2.unwrap() as i32];
     68         }
     69     }
     70     Vec::new()
     71 }
     72 
     73 // Can we change our array somehow so the search become faster?
     74 // 3ms 2.29MB
     75 pub fn two_sum_5(nums: Vec<i32>, target: i32) -> Vec<i32> {
     76     let mut arr = nums.clone();
     77     for _ in nums.iter() {
     78         let last = arr.pop();
     79         if let Some(last) = last {
     80             let result = target - last;
     81             let i1 = nums.iter().rposition(|x| x == &last);
     82             let i2 = arr.iter().position(|y| y == &result);
     83             if let (Some(i1), Some(i2)) = (i1, i2) {
     84                 return vec![i2 as i32, i1 as i32];
     85             }
     86         }
     87     }
     88     Vec::new()
     89 }
     90 
     91 // Without changing the array, can use additional space to speed up the search?
     92 // 65ms 2.54MB
     93 pub fn two_sum_6(nums: Vec<i32>, target: i32) -> Vec<i32> {
     94     let mut map: std::collections::HashMap<_, _> = nums.iter().enumerate().collect();
     95     for (i1, x) in nums.iter().enumerate() {
     96         map.remove(&i1);
     97         let result = target - x;
     98         if let Some((i2, _)) = &map.iter().find(|(_, v)| ***v == result) {
     99             return vec![i1 as i32, **i2 as i32];
    100         }
    101     }
    102     Vec::new()
    103 }
    104 
    105 // 3ms 3.2MB
    106 pub fn two_sum_7(nums: Vec<i32>, target: i32) -> Vec<i32> {
    107     let map = nums.iter().enumerate().fold(
    108         std::collections::HashMap::<i32, Vec<i32>>::new(),
    109         |mut map, (idx, &val)| {
    110             map.entry(val).or_default().push(idx as i32);
    111             map
    112         },
    113     );
    114 
    115     let mut answer = std::collections::HashSet::<i32>::new();
    116 
    117     for (i, x) in map.iter() {
    118         let result = target - i;
    119         if result == *i && x.len().eq(&1) {
    120             continue;
    121         };
    122         let index = map.get(&result);
    123         if let Some(index) = index {
    124             answer.extend(index);
    125         }
    126     }
    127     answer.into_iter().collect()
    128 }
    129 
    130 // 1ms 3.11MB
    131 // Sometimes: 0ms 3.02MB
    132 //
    133 // This solution thinks in terms of pairs: build a complete map of everything,
    134 // then search for matching pairs. This is natural but leads to complexity —
    135 // you have to handle edge cases like duplicate values, and you process the
    136 // whole array before finding anything.
    137 pub fn two_sum_8(nums: Vec<i32>, target: i32) -> Vec<i32> {
    138     if nums.len() == 2 {
    139         return [0, 1].to_vec();
    140     }
    141 
    142     let map = nums.iter().enumerate().fold(
    143         std::collections::HashMap::<i32, Vec<i32>>::new(),
    144         |mut map, (idx, &val)| {
    145             map.entry(val).or_default().push(idx as i32);
    146             map
    147         },
    148     );
    149 
    150     let result: Vec<i32> = map
    151         .iter()
    152         .filter_map(|(value, keys)| {
    153             let complement = target - value;
    154             // Skip the case where the value pairs with itself but only has one index
    155             if complement == *value && keys.len() == 1 {
    156                 return None;
    157             }
    158             map.get(&complement)
    159         })
    160         .flat_map(|indices| indices.iter().copied())
    161         .collect();
    162 
    163     result
    164 }
    165 
    166 // 0ms 2.56MB
    167 //
    168 // Whenever you build a map over a whole collection before querying it,
    169 // ask yourself: "Could I merge the build and query into one pass?"
    170 //
    171 // The optimal solution asks a different question at each step:
    172 // "Have I already seen the number I need?"
    173 //
    174 // Think of it this way: you're walking through the array left to right.
    175 // At any position i, the map contains only the elements you've already passed.
    176 // You're not comparing against the whole array — you're asking: "among everything
    177 // I've seen so far, does my complement exist?"
    178 pub fn two_sum_9(nums: Vec<i32>, target: i32) -> Vec<i32> {
    179     let mut seen = std::collections::HashMap::<i32, usize>::new();
    180 
    181     for (i, x) in nums.iter().enumerate() {
    182         let result = target - x;
    183 
    184         let y = seen.get(&result);
    185 
    186         if let Some(y) = y {
    187             return vec![*y as i32, i as i32];
    188         }
    189 
    190         seen.insert(*x, i);
    191     }
    192 
    193     Vec::new()
    194 }
    195 
    196 // #[cfg(test)]
    197 // mod tests {
    198 //     use crate::hash_table::two_sum::{
    199 //         two_sum_1, two_sum_2, two_sum_3, two_sum_4, two_sum_5, two_sum_6, two_sum_7, two_sum_8,
    200 //         two_sum_9,
    201 //     };
    202 //     use crate::test_utils::benchmark::{init_benchmark_tracing, run_and_assert_vec_any_order};
    203 
    204 //     #[derive(Clone)]
    205 //     struct TwoSumCase {
    206 //         name: &'static str,
    207 //         nums: Vec<i32>,
    208 //         target: i32,
    209 //         expected: Vec<i32>,
    210 //     }
    211 
    212 //     fn full_cases() -> Vec<TwoSumCase> {
    213 //         vec![
    214 //             TwoSumCase {
    215 //                 name: "case_1",
    216 //                 nums: vec![2, 7, 11, 15],
    217 //                 target: 9,
    218 //                 expected: vec![0, 1],
    219 //             },
    220 //             TwoSumCase {
    221 //                 name: "case_2",
    222 //                 nums: vec![3, 2, 3],
    223 //                 target: 6,
    224 //                 expected: vec![0, 2],
    225 //             },
    226 //             TwoSumCase {
    227 //                 name: "case_3",
    228 //                 nums: vec![3, 2, 4],
    229 //                 target: 6,
    230 //                 expected: vec![1, 2],
    231 //             },
    232 //             TwoSumCase {
    233 //                 name: "case_4",
    234 //                 nums: vec![-3, 4, 3, 90],
    235 //                 target: 0,
    236 //                 expected: vec![0, 2],
    237 //             },
    238 //         ]
    239 //     }
    240 
    241 //     fn run_solver_cases(
    242 //         solver_name: &str,
    243 //         solver: fn(Vec<i32>, i32) -> Vec<i32>,
    244 //         cases: Vec<TwoSumCase>,
    245 //     ) {
    246 //         init_benchmark_tracing();
    247 //         for case in cases {
    248 //             run_and_assert_vec_any_order(
    249 //                 &format!("{solver_name}_{}", case.name),
    250 //                 case.expected,
    251 //                 || solver(case.nums, case.target),
    252 //             );
    253 //         }
    254 //     }
    255 
    256 //     #[test]
    257 //     #[ignore]
    258 //     fn two_sum_1_cases() {
    259 //         run_solver_cases("two_sum_1", two_sum_1, full_cases());
    260 //     }
    261 
    262 //     #[test]
    263 //     #[ignore]
    264 //     fn two_sum_2_cases() {
    265 //         run_solver_cases("two_sum_2", two_sum_2, full_cases());
    266 //     }
    267 
    268 //     #[test]
    269 //     #[ignore]
    270 //     fn two_sum_3_cases() {
    271 //         run_solver_cases("two_sum_3", two_sum_3, full_cases());
    272 //     }
    273 
    274 //     #[test]
    275 //     #[ignore]
    276 //     fn two_sum_4_cases() {
    277 //         run_solver_cases("two_sum_4", two_sum_4, full_cases());
    278 //     }
    279 
    280 //     #[test]
    281 //     #[ignore]
    282 //     fn two_sum_5_cases() {
    283 //         run_solver_cases("two_sum_5", two_sum_5, full_cases());
    284 //     }
    285 
    286 //     #[test]
    287 //     #[ignore]
    288 //     fn two_sum_6_cases() {
    289 //         run_solver_cases("two_sum_6", two_sum_6, full_cases());
    290 //     }
    291 
    292 //     #[test]
    293 //     #[ignore]
    294 //     fn two_sum_7_cases() {
    295 //         run_solver_cases("two_sum_7", two_sum_7, full_cases());
    296 //     }
    297 
    298 //     #[test]
    299 //     #[ignore]
    300 //     fn two_sum_8_cases() {
    301 //         run_solver_cases("two_sum_8", two_sum_8, full_cases());
    302 //     }
    303 
    304 //     #[test]
    305 //     #[ignore]
    306 //     fn two_sum_9_cases() {
    307 //         run_solver_cases("two_sum_9", two_sum_9, full_cases());
    308 //     }
    309 // }