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 // }