├── .github └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.toml ├── README.md ├── puzzles.md ├── sql_problems ├── apples_oranges.sql ├── duplicate_emails.sql ├── invert_sex.sql ├── second_highest_salary.sql ├── shortest_distance_in_a_line.sql └── very_easy.sql └── src ├── bfs_dfs_backtracking ├── all_paths_from_source_to_target.rs ├── combination_sum_1_2.rs ├── combination_sum_iii.rs ├── combinations_subsets.rs ├── dijkstra.rs ├── flatten_nested_list_iterator.rs ├── letter_combinations_of_a_phone_number.rs ├── lexicographical_numbers.rs ├── max_area_of_island.rs ├── mod.rs ├── n_queens.rs ├── num_ways.rs ├── permutations.rs ├── restore_ip_address.rs ├── surrounded_regions.rs └── the_maze.rs ├── binary_search ├── median_of_two_sorted_arrays.rs ├── mod.rs ├── mountain_array.rs ├── search_a_2d_matrix.rs └── single_element_in_a_sorted_array.rs ├── binary_tree ├── evaluate_boolean_binary_tree.rs ├── graphviz_view_leetcode_binary_tree.rs ├── invert_binary_tree.rs ├── is_bst.rs ├── leaf_similar_trees.rs ├── level_order_traversal.rs ├── max_depth_of_binary_tree.rs ├── merge_two_binary_trees.rs ├── mod.rs ├── preorder_traversal.rs ├── same_tree.rs ├── search_val_or_range_in_bst.rs ├── serde_binary_tree_to_leetcode_vec.rs ├── serde_binary_tree_to_parentheses_str.rs ├── std_ops_controlflow_in_binary_tree.rs ├── sum_of_left_leaves.rs ├── sum_root_to_leaf_numbers.rs └── univalued_binary_tree.rs ├── bitwise ├── find_single_number.rs ├── gray_code.rs ├── hamming_distance_count_ones.rs ├── is_power_of_x.rs ├── mod.rs ├── nim_game.rs └── reverse_bits.rs ├── code_snippets ├── lazy_static.rs ├── mod.rs ├── python_and_c_diff_when_calc_negative_number.rs ├── random_i32.rs └── sorting.rs ├── compiler ├── basic_calculator_ii_no_parentheses.rs ├── evaluate_reverse_polish_notation.rs └── mod.rs ├── counter ├── anagrams.rs ├── count_good_meals.rs ├── count_num1_square_eq_two_num2_product.rs ├── find_all_numbers_disappeared_in_an_array.rs ├── increasing_decreasing_string.rs ├── mod.rs ├── number_of_equivalent_domino_pairs.rs └── number_of_good_pairs.rs ├── data_structure ├── heap │ ├── kth_largest_element_in_a_stream.rs │ ├── mod.rs │ ├── my_max_heap.rs │ ├── seat_reservation_manager.rs │ ├── single_threaded_cpu.rs │ ├── sliding_window_median.rs │ └── top_k_frequent_elements.rs ├── mod.rs ├── monotonic_queue_sliding_window_max.rs ├── monotonic_stack_next_greater_element_2.rs ├── trie_edit_distance.rs └── union_find │ ├── friend_circles.rs │ ├── min_cost_to_connect_all_points.rs │ └── mod.rs ├── dp ├── burst_balloons.rs ├── calculate_minimum_hp.rs ├── coin_change.rs ├── counting_bits.rs ├── dp_easy.rs ├── drop_eggs.rs ├── edit_distance.rs ├── fibonacci.rs ├── freedom_trail.rs ├── jump_game_ii.rs ├── longest_common_substr.rs ├── longest_palindromic_substr.rs ├── minimum_falling_path_sum.rs ├── mod.rs ├── number_of_ways_to_stay_in_the_same_place_after_some_steps.rs ├── stone_game.rs ├── trapping_rain_water.rs ├── triangle.rs └── unique_paths.rs ├── easy ├── array │ ├── find_numbers_with_even_number_of_digits.rs │ ├── mod.rs │ ├── partition_array.rs │ ├── queries_on_a_permutation_with_key.rs │ ├── squares_of_a_sorted_array.rs │ └── sum_of_all_odd_length_subarrays.rs ├── codeforces_easy.rs ├── grid_or_matrix │ ├── count_negative_numbers_in_a_sorted_matrix.rs │ ├── island_perimeter.rs │ ├── matrix_diagonal_traverse.rs │ ├── mod.rs │ ├── rotate_matrix.rs │ └── spiral_matrix.rs ├── leetcode_easy.rs ├── leetcode_very_easy.rs ├── mod.rs └── string │ ├── find_common_characters.rs │ ├── long_pressed_name.rs │ ├── mod.rs │ └── parse_ip_address.rs ├── graph ├── mod.rs └── topological_sorting.rs ├── greedy ├── airplane_seat_assignment_probability.rs ├── candy.rs ├── container_with_most_water.rs ├── count_number_of_nice_subarrays.rs ├── di_string_match.rs ├── factorial_trailing_zeroes.rs ├── gas_station.rs ├── int_to_roman.rs ├── majority_element.rs ├── maximum_subarray.rs ├── mod.rs ├── partition_labels.rs ├── prefix_sum_subarray.rs └── product_of_array_except_self.rs ├── lib.rs ├── linked_list ├── add_two_linked_list.rs ├── insertion_sort_linked_list.rs ├── is_circular_loop.rs ├── linked_list_is_palindrome.rs ├── merge_two_sorted_linked_list.rs ├── middle_of_linked_list.rs ├── mod.rs ├── nth_node_from_end.rs ├── partition_list.rs ├── plus_one.rs ├── reverse_linked_list.rs ├── reverse_linked_list_2.rs └── swap_nodes_in_pairs.rs ├── macros.rs ├── math ├── count_primes.rs ├── excel_sheet_column_title.rs ├── mod.rs ├── perfect_number.rs ├── pow.rs └── sqrt.rs ├── random ├── impl_rand10_by_rand7.rs ├── mod.rs └── random_pick_index.rs ├── two_sum_two_pointers ├── four_sum_2.rs ├── mod.rs ├── two_sum.rs ├── two_sum_relative.rs └── valid_triangle_number.rs └── uncategorized ├── contains_substr_kmp.rs ├── longest_non_repeated_substr.rs ├── mod.rs ├── reverse_integer_checked_mul_overflow.rs └── string_in_place.rs /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: check_lint_test 2 | on: [push] 3 | #env: 4 | # TOOLCHAIN: nightly-2022-03-01-x86_64-unknown-linux-gnu 5 | 6 | jobs: 7 | typo_check_lint_test: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout Actions Repository 11 | uses: actions/checkout@v2 12 | 13 | - name: typo 14 | uses: crate-ci/typos@v1.12.12 15 | 16 | - name: install nightly toolchain 17 | uses: actions-rs/toolchain@v1 18 | with: 19 | toolchain: nightly 20 | profile: minimal 21 | components: rustfmt, clippy 22 | override: true 23 | - name: check 24 | uses: actions-rs/cargo@v1 25 | with: 26 | toolchain: nightly 27 | command: check 28 | args: --tests 29 | - name: clippy 30 | uses: actions-rs/cargo@v1 31 | with: 32 | toolchain: nightly 33 | command: clippy 34 | args: --tests 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | Cargo.lock 3 | .idea 4 | .vscode 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "leetcode-rust" 3 | version = "0.1.1" 4 | edition = "2021" 5 | license = "MIT" 6 | repository = "https://github.com/pymongo/leetcode-rust/" 7 | keywords = ["leetcode", "codeforce"] 8 | categories = ["algorithm"] 9 | description = "leetcode/codeforce Rust solutions" 10 | 11 | [features] 12 | rustc_private = [] 13 | default = [] 14 | # default = ["rustc_private"] 15 | # [package.metadata.rust-analyzer] 16 | # rustc_private = true 17 | -------------------------------------------------------------------------------- /puzzles.md: -------------------------------------------------------------------------------- 1 | # 益智问题 2 | 3 | ## 狼羊菜过河(DFS) 4 | 5 | 6 | 7 | ## 3狼3羊过河 8 | 9 | 农夫每次可以带1-2个动物过河,河两岸每边如果狼的数量大于羊,则狼会吃掉羊 10 | 11 | - 12 | - 13 | - 14 | 15 | ## 海盗分金币问题(比较脑筋急转弯的递推) 16 | -------------------------------------------------------------------------------- /sql_problems/apples_oranges.sql: -------------------------------------------------------------------------------- 1 | -- https://leetcode.com/problems/apples-oranges/ 2 | -- SUM(case when fruit='apples' then sold_num else -sold_num end) 3 | select 4 | sale_date, 5 | sum( if( fruit='apples', sold_num, -sold_num ) ) as diff 6 | from 7 | Sales 8 | group by 9 | sale_date 10 | ; 11 | -------------------------------------------------------------------------------- /sql_problems/duplicate_emails.sql: -------------------------------------------------------------------------------- 1 | -- https://leetcode.com/problems/duplicate-emails/ 2 | -- 我一开是的思路先用distinct email查一次,结果记为a,然后查所有email记为b 3 | -- 通过MINUS operator得到b-a,最后SELECT DISTINCT email FROM b-a 4 | -- 但是「MySQL不支持MINUS」求差集的操作 5 | -- 按邮箱分组后用having语句查count(email)>1的 6 | select email 7 | from person 8 | group by email 9 | having count(email) > 1; 10 | -------------------------------------------------------------------------------- /sql_problems/invert_sex.sql: -------------------------------------------------------------------------------- 1 | -- https://leetcode.com/problems/swap-salary/ 2 | update users 3 | set sex = if(sex = 'male', 'female', 'male'); 4 | 5 | -- 方法2,使用case when语句 6 | UPDATE users 7 | SET 8 | sex = CASE sex 9 | WHEN 'male' THEN 'female' 10 | ELSE 'male' 11 | END; 12 | -------------------------------------------------------------------------------- /sql_problems/second_highest_salary.sql: -------------------------------------------------------------------------------- 1 | -- https://leetcode.com/problems/second-highest-salary/ 2 | -- 本题的难点在于如果不存在排第二的工资则返回NULL,所以要包了一层IFNULL 3 | SELECT IFNULL( 4 | ( 5 | SELECT DISTINCT salary 6 | FROM employee 7 | ORDER BY salary DESC 8 | LIMIT 1 OFFSET 1 9 | ), 10 | NULL 11 | ) AS SecondHighestSalary 12 | -------------------------------------------------------------------------------- /sql_problems/shortest_distance_in_a_line.sql: -------------------------------------------------------------------------------- 1 | -- https://leetcode.com/problems/shortest-distance-in-a-line/ 2 | -- 一开始我还以为要用MySQL的window函数 3 | 4 | -- 解法一 5 | SELECT (p1.x - p2.x) AS shortest 6 | FROM point AS p1, point AS p2 7 | WHERE p1.x > p2.x 8 | ORDER BY shortest 9 | LIMIT 1; 10 | 11 | -- 解法二 12 | SELECT MIN(ABS(p1.x - p2.x)) AS shortest 13 | FROM point AS p1, point AS p2 14 | WHERE p1.x != p2.x; 15 | 16 | SELECT MIN(ABS(p1.x - p2.x)) AS shortest 17 | FROM point AS p1 JOIN point AS p2 18 | ON p1.x != p2.x; 19 | -------------------------------------------------------------------------------- /sql_problems/very_easy.sql: -------------------------------------------------------------------------------- 1 | -- https://leetcode.com/problems/employees-earning-more-than-their-managers/ 2 | select e.Name as Employee 3 | from Employee as e 4 | inner join Employee as m 5 | on e.ManagerId = m.Id 6 | where e.Salary > m.Salary; 7 | 8 | -- https://leetcode.com/problems/not-boring-movies/ 9 | select id,movie,description,rating 10 | from cinema 11 | where id%2=1 and description <> 'boring' 12 | order by rating desc; 13 | 14 | -- https://leetcode.com/problems/invalid-tweets/ 15 | select tweet_id from Tweets where length(content) > 15; 16 | 17 | -- https://leetcode.com/problems/find-total-time-spent-by-each-employee/ 18 | select 19 | event_day as day, 20 | emp_id, 21 | sum(out_time-in_time) as total_time 22 | from 23 | Employees 24 | group by 25 | event_day, 26 | emp_id 27 | ; 28 | 29 | -- https://leetcode.com/problems/find-the-team-size/ 30 | -- select e1.employee_id, count(*) as team_size from Employee e1, Employee e2 where e1.team_id = e2.team_id group by e1.employee_id; 31 | -- select employee_id, count(*) over(partition by team_id) as team_size from employee 32 | select 33 | a.employee_id as employee_id, 34 | b.team_size as team_size 35 | from 36 | Employee as a 37 | left join 38 | ( 39 | select 40 | team_id, 41 | count(employee_id) as team_size 42 | from 43 | Employee 44 | group by 45 | team_id 46 | ) as b 47 | on 48 | a.team_id = b.team_id 49 | ; 50 | 51 | -- https://leetcode.com/problems/average-selling-price/ 52 | select 53 | p.product_id, 54 | round(sum(p.price * u.units) / sum(u.units), 2) as average_price 55 | from 56 | Prices as p, 57 | UnitsSold as u 58 | where 59 | p.product_id = u.product_id 60 | and u.purchase_date between start_date and end_date 61 | group by 62 | p.product_id 63 | ; 64 | 65 | -- https://leetcode.com/problems/product-sales-analysis-i/ 66 | -- select product_name, year, price from Sales left join Product using (product_id) 67 | -- join on 后面的条件已经把结果过滤了一遍,而where是笛卡尔积后才根据限制条件进行过滤,所以join性能要比where好 68 | select p.product_name, s.year, s.price 69 | from Sales as s, Product as p 70 | where s.product_id = p.product_id; 71 | 72 | -- https://leetcode.com/problems/product-sales-analysis-ii/ 73 | select product_id, sum(quantity) as total_quantity from Sales group by product_id; 74 | 75 | -- https://leetcode.com/problems/triangle-judgement/ 76 | -- SELECT x,y,z,(CASE WHEN x+y>z AND x+z>y AND z+y>x THEN 'Yes' ELSE 'No' END) as triangle FROM triangle; 77 | select 78 | x, y, z, 79 | -- 两最小边之和大于最长边,则能构成三角形 80 | if( x+y+z-greatest(x,y,z)>greatest(x,y,z), 'Yes', 'No' ) as triangle 81 | from triangle; 82 | -------------------------------------------------------------------------------- /src/bfs_dfs_backtracking/all_paths_from_source_to_target.rs: -------------------------------------------------------------------------------- 1 | /// https://leetcode.com/problems/all-paths-from-source-to-target/ 2 | 3 | struct AllPathsSourceTargetHelper { 4 | graph: *const Vec>, 5 | dest: i32, 6 | cur: Vec, 7 | paths: Vec>, 8 | } 9 | 10 | impl AllPathsSourceTargetHelper { 11 | fn dfs(&mut self, cur: i32) { 12 | if cur == self.dest { 13 | self.paths.push(self.cur.clone()); 14 | return; 15 | } 16 | for &next in &unsafe { &*self.graph }[cur as usize] { 17 | self.cur.push(next); 18 | self.dfs(next); 19 | self.cur.pop().unwrap(); 20 | } 21 | } 22 | } 23 | 24 | /// https://leetcode.com/problems/all-paths-from-source-to-target/ 25 | fn all_paths_source_target_my_best(graph: Vec>) -> Vec> { 26 | let dest = graph.len() as i32 - 1; 27 | let mut helper = AllPathsSourceTargetHelper { 28 | graph: &graph as *const _, 29 | dest, 30 | cur: vec![0], 31 | paths: Vec::new(), 32 | }; 33 | helper.dfs(0); 34 | helper.paths 35 | } 36 | 37 | /// 由于Rust闭包不能或者很难递归调用,不用闭包又不能捕获外部作用域的变量,不能像python那样方便的内层定义的方法能随意读写外层的变量 38 | /// 导致Rust写回溯算法的题非常困难,dfs函数入参太多,一定要头脑清醒从一开始就想清楚宏观的需要几个入参,哪些入参要回溯 39 | /// 如果用结构体去重构超多入参的dfs函数,会导致不能以更细粒度的状态进行回溯,结构体不能更细粒度和高性能的记住上一个状态 40 | fn all_paths_source_target(graph: Vec>) -> Vec> { 41 | let graph: Vec> = graph 42 | .into_iter() 43 | .map(|each| each.into_iter().map(|x| x as usize).collect()) 44 | .collect(); 45 | let n = graph.len(); 46 | let mut curr = Vec::with_capacity(n); 47 | let mut ret = Vec::new(); 48 | let mut visited = vec![false; n]; 49 | // for start in 0..n { 50 | curr.push(0); 51 | visited[0] = true; 52 | dfs(&mut curr, &mut visited, &mut ret, &graph, n - 1); 53 | // curr.pop().unwrap(); 54 | // visited[start] = false; 55 | // } 56 | ret.into_iter() 57 | .map(|each| each.into_iter().map(|x| x as i32).collect()) 58 | .collect() 59 | } 60 | 61 | fn dfs( 62 | curr: &mut Vec, 63 | visited: &mut Vec, 64 | ret: &mut Vec>, 65 | graph: &[Vec], 66 | dest: usize, 67 | ) { 68 | for &next in &graph[*curr.last().unwrap()] { 69 | if visited[next] { 70 | continue; 71 | } 72 | curr.push(next); 73 | if next == dest { 74 | ret.push(curr.clone()); 75 | curr.pop().unwrap(); 76 | continue; 77 | } 78 | visited[next] = true; 79 | dfs(curr, visited, ret, graph, dest); 80 | curr.pop().unwrap(); 81 | visited[next] = false; 82 | } 83 | } 84 | 85 | #[test] 86 | fn test_all_paths_source_target() { 87 | // 入参graph的数据格式是邻接表,graph[0]表示节点0的连向节点1和节点2 88 | let test_cases = vec![( 89 | vec_vec![[1, 2], [3], [3], []], 90 | vec_vec![[0, 1, 3], [0, 2, 3]], 91 | )]; 92 | for (input, output) in test_cases { 93 | assert_eq!(all_paths_source_target(input), output); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/bfs_dfs_backtracking/combination_sum_1_2.rs: -------------------------------------------------------------------------------- 1 | struct Solution; 2 | 3 | impl Solution { 4 | fn combination_sum2(mut nums: Vec, target: i32) -> Vec> { 5 | // .sort()调用归并排序,需要额外空间,建议使用sort_unstable()速度更快而且无需额外空间() 6 | nums.sort_unstable(); 7 | let n = nums.len(); 8 | let mut cur = Vec::with_capacity(n); 9 | let mut res = Vec::new(); 10 | Self::helper(0, target, &mut cur, &mut res, &nums, n); 11 | res 12 | } 13 | 14 | fn helper( 15 | start: usize, 16 | target: i32, 17 | cur: &mut Vec, 18 | res: &mut Vec>, 19 | nums: &[i32], 20 | n: usize, 21 | ) { 22 | if target == 0 { 23 | res.push(cur.clone()); 24 | } 25 | for i in start..n { 26 | // 剪枝: 由于数组是有序的,如果当前的值比target大,则往后的值也是比target大 27 | if nums[i] > target { 28 | return; 29 | } 30 | // 剪枝: 答案集去重,避免重复解 31 | if i > start && nums[i] == nums[i - 1] { 32 | continue; 33 | } 34 | cur.push(nums[i]); 35 | // combination_sum_1: 允许nums中元素重复使用,所以start_index会是i 36 | // Self::helper(i, target-nums[i], cur, res, &nums, n); 37 | // combination_sum_2: 不允许nums中元素重复使用,所以start_index会是i+1 38 | Self::helper(i + 1, target - nums[i], cur, res, nums, n); 39 | cur.pop(); 40 | } 41 | } 42 | } 43 | 44 | #[test] 45 | fn test() { 46 | let res = Solution::combination_sum2(vec![2, 5, 2, 1, 2], 5); 47 | for row in res { 48 | println!("{:?}", row); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/bfs_dfs_backtracking/combination_sum_iii.rs: -------------------------------------------------------------------------------- 1 | /// https://leetcode.com/problems/combination-sum-iii/ 2 | /// 从1-9中找出k个不重复的组合,使得它们的和等于target 3 | fn combination_sum_iii(k: i32, target: i32) -> Vec> { 4 | let k = k as usize; 5 | let mut cur = Vec::with_capacity(k); 6 | let mut res = Vec::new(); 7 | helper(1, target, &mut cur, &mut res, k); 8 | res 9 | } 10 | 11 | fn helper(start: i32, target: i32, cur: &mut Vec, res: &mut Vec>, k: usize) { 12 | if cur.len() == k { 13 | if target == 0 { 14 | res.push(cur.clone()); 15 | } 16 | return; 17 | } 18 | for num in start..=9 { 19 | if num > target { 20 | return; 21 | } 22 | cur.push(num); 23 | helper(num + 1, target - num, cur, res, k); 24 | cur.pop(); 25 | } 26 | } 27 | 28 | #[test] 29 | fn test_combination_sum_iii() { 30 | let test_cases = vec![ 31 | (3, 7, vec_vec![[1, 2, 4]]), 32 | (3, 9, vec_vec![[1, 2, 6], [1, 3, 5], [2, 3, 4]]), 33 | ]; 34 | for (k, target, output) in test_cases { 35 | assert_eq!(combination_sum_iii(k, target), output); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/bfs_dfs_backtracking/flatten_nested_list_iterator.rs: -------------------------------------------------------------------------------- 1 | //! https://leetcode.com/problems/flatten-nested-list-iterator/ 2 | #[derive(Debug, PartialEq, Eq)] 3 | pub enum NestedInteger { 4 | Int(i32), 5 | List(Vec), 6 | } 7 | 8 | /** 9 | 借助队列或栈模拟递归的解法 10 | ```python 11 | def flatten(lists): 12 | ret = [] 13 | q = collections.deque(lists) 14 | while q: 15 | item = q.popleft() 16 | if isinstance(item, list): 17 | # 保证重新扔回队列头部时是按照数组原有顺序 18 | for each in reversed(item): 19 | q.appendleft(each) 20 | continue 21 | ret.append(item) 22 | return ret 23 | ``` 24 | */ 25 | fn flatten_dfs(list: Vec) -> Vec { 26 | list.into_iter() 27 | .flat_map(|item| match item { 28 | NestedInteger::Int(int) => vec![int], 29 | NestedInteger::List(list) => flatten_dfs(list), 30 | }) 31 | .collect() 32 | } 33 | 34 | struct NestedIterator { 35 | cursor: usize, 36 | len: usize, 37 | nums: Vec, 38 | } 39 | 40 | impl NestedIterator { 41 | fn new(list: Vec) -> Self { 42 | // use flattern to merge multi arrays? 43 | let nums = flatten_dfs(list); 44 | Self { 45 | cursor: 0, 46 | len: nums.len(), 47 | nums, 48 | } 49 | } 50 | 51 | fn next(&mut self) -> i32 { 52 | let ret = self.nums[self.cursor]; 53 | self.cursor += 1; 54 | ret 55 | } 56 | 57 | fn has_next(&mut self) -> bool { 58 | self.cursor < self.len 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/bfs_dfs_backtracking/letter_combinations_of_a_phone_number.rs: -------------------------------------------------------------------------------- 1 | /// https://leetcode.com/problems/letter-combinations-of-a-phone-number/ 2 | /// 通过last_combs/curr_combs(有点像dp里的滚动数组),也有点像双队列level_order遍历,生成product笛卡尔积 3 | /// 当然也可以用sentry_node的deque去遍历,只是我觉得频繁popleft性能不太好 4 | fn letter_combinations_bfs(digits: String) -> Vec { 5 | const KEYMAP: [&[u8]; 10] = [ 6 | &[], // 0 7 | &[], // 1 8 | &[b'a', b'b', b'c'], // 2 9 | &[b'd', b'e', b'f'], 10 | &[b'g', b'h', b'i'], 11 | &[b'j', b'k', b'l'], 12 | &[b'm', b'n', b'o'], 13 | &[b'p', b'q', b'r', b's'], 14 | &[b't', b'u', b'v'], 15 | &[b'w', b'x', b'y', b'z'], // 9 16 | ]; 17 | if digits.is_empty() { 18 | return Vec::with_capacity(0); 19 | } 20 | let mut last_combs = vec![vec![]]; 21 | for digit in digits.into_bytes() { 22 | let mut curr_combs = Vec::with_capacity(last_combs.len() * 4); 23 | for last_comb in last_combs { 24 | // 这里必须要克隆,因为例如会将一份last_comb="a"给拼成"ad","ae","af" 25 | for &letter in KEYMAP[(digit - b'0') as usize] { 26 | let mut curr_comb = last_comb.clone(); 27 | curr_comb.push(letter); 28 | curr_combs.push(curr_comb); 29 | } 30 | } 31 | last_combs = curr_combs; 32 | } 33 | last_combs 34 | .into_iter() 35 | .map(|each| unsafe { String::from_utf8_unchecked(each) }) 36 | .collect() 37 | } 38 | 39 | // 用DFS回溯的写法会麻烦很多 40 | 41 | #[test] 42 | fn test_letter_combinations() { 43 | const TEST_CASES: [(&str, &[&str]); 1] = [( 44 | "23", 45 | &["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"], 46 | )]; 47 | for (input, output) in TEST_CASES { 48 | assert_eq!(letter_combinations_bfs(input.into()), output); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/bfs_dfs_backtracking/lexicographical_numbers.rs: -------------------------------------------------------------------------------- 1 | /// https://leetcode.com/problems/lexicographical-numbers/ 2 | /// 字典序排列从1..=n的数字,类似windows file_explorer中文件名会按 1.mp4, 10.mp4, 2.mp4这样的顺序排序 3 | /// 好像跟字典序关系不大,例如python排序后也是这样,因为数字转字符串后排序本身就会是这样的 4 | /// 看测试用例就知道可能要递归,(10进制)10叉树的DFS-回溯 5 | fn lexical_order(n: i32) -> Vec { 6 | fn dfs(num: i32, end: i32, ret: &mut Vec) { 7 | if num > end { 8 | return; 9 | } 10 | ret.push(num); 11 | // digit=个位 12 | for digit in 0..=9 { 13 | dfs(num * 10 + digit, end, ret); 14 | } 15 | } 16 | 17 | let mut ret = Vec::new(); 18 | // 注意10叉树的根节点没有0(第一位不能是0) 19 | for root in 1..=9 { 20 | dfs(root, n, &mut ret); 21 | } 22 | ret 23 | } 24 | 25 | #[test] 26 | fn test_lexical_order() { 27 | const TEST_CASES: [(i32, &[i32]); 2] = [ 28 | (3, &[1, 2, 3]), 29 | (13, &[1, 10, 11, 12, 13, 2, 3, 4, 5, 6, 7, 8, 9]), 30 | ]; 31 | for (input, output) in TEST_CASES { 32 | assert_eq!(lexical_order(input), output); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/bfs_dfs_backtracking/max_area_of_island.rs: -------------------------------------------------------------------------------- 1 | /*! https://leetcode.com/problems/max-area-of-island 2 | VecDeque双端队列内部通过ring buffer环形数组实现 3 | 在rust的1.21版本之前以下函数是错的 4 | fn is_full(&self) -> bool { 5 | self.cap() - self.len() == 1 6 | } 7 | 因为环状数组,tail在head的前一位索引时表示已满,所以is_full方法内需要对cap()逻辑容量进行-1后再跟物理容量len去比较 8 | */ 9 | 10 | /// 跟`Number of Islands`一题中判断is_island的过程类似,只是要返回的数据不同 11 | fn max_area_of_island(mut grid: Vec>) -> i32 { 12 | let (m, n) = (grid.len(), grid[0].len()); 13 | let mut queue = std::collections::VecDeque::new(); 14 | let mut max_island_area = 0; 15 | for i in 0..m { 16 | for j in 0..n { 17 | if grid[i][j] != 1 { 18 | continue; 19 | } 20 | queue.push_back((i, j)); 21 | let mut curr_island_area_count = 0; 22 | while let Some((x, y)) = queue.pop_front() { 23 | if grid[x][y] == 2 { 24 | // 如果(0,1)和(1,0)都连向(1,1) 则(1,1)会被重复算一次,所以这里「也要」过滤掉已被访问过的节点 25 | continue; 26 | } 27 | // 将访问过的点标记为2,避免图中有环导致重复遍历陷入死循环 28 | grid[x][y] = 2; 29 | curr_island_area_count += 1; 30 | // up and down 31 | if x > 0 && grid[x - 1][y] == 1 { 32 | queue.push_back((x - 1, y)); 33 | } 34 | if x < m - 1 && grid[x + 1][y] == 1 { 35 | queue.push_back((x + 1, y)); 36 | } 37 | // left and right 38 | if y > 0 && grid[x][y - 1] == 1 { 39 | queue.push_back((x, y - 1)); 40 | } 41 | if y < n - 1 && grid[x][y + 1] == 1 { 42 | queue.push_back((x, y + 1)); 43 | } 44 | } 45 | max_island_area = max_island_area.max(curr_island_area_count); 46 | } 47 | } 48 | max_island_area 49 | } 50 | 51 | #[cfg(test)] 52 | const TEST_CASES: [(&[&[i32]], i32); 1] = [( 53 | &[ 54 | &[1, 1, 0, 0, 0], 55 | &[1, 1, 0, 0, 0], 56 | &[0, 0, 0, 1, 1], 57 | &[0, 0, 0, 1, 1], 58 | ], 59 | 4, 60 | )]; 61 | 62 | #[test] 63 | fn test_max_area_of_island() { 64 | for (grid, max_area) in TEST_CASES { 65 | let grid: Vec> = grid.iter().map(|each| each.to_vec()).collect(); 66 | assert_eq!(max_area_of_island(grid), max_area); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/bfs_dfs_backtracking/mod.rs: -------------------------------------------------------------------------------- 1 | // 很多BFS的题都能用DFS(回溯)来做,所以归为一类题型 2 | mod all_paths_from_source_to_target; 3 | mod combination_sum_1_2; 4 | mod combination_sum_iii; 5 | mod combinations_subsets; 6 | mod dijkstra; 7 | mod flatten_nested_list_iterator; 8 | mod letter_combinations_of_a_phone_number; 9 | mod lexicographical_numbers; 10 | mod max_area_of_island; 11 | mod n_queens; 12 | mod num_ways; 13 | mod permutations; 14 | mod restore_ip_address; 15 | mod surrounded_regions; 16 | mod the_maze; 17 | -------------------------------------------------------------------------------- /src/bfs_dfs_backtracking/n_queens.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | /// https://leetcode.com/problems/n-queens-ii 4 | fn n_queens_ii(n: i32) -> Vec> { 5 | let n = n as usize; 6 | // 索引表示皇后的横坐标,值表示皇后的列坐标 7 | let mut queens: Vec = Vec::with_capacity(n); 8 | // 已使用的列号 9 | let mut used_cols: Vec = vec![false; n]; 10 | // 存储直线方程x+y的常系数 11 | let mut sum: HashSet = HashSet::with_capacity(n); 12 | // 存储直线方程x-y的常系数 13 | let mut dif: HashSet = HashSet::with_capacity(n); 14 | 15 | let mut ret: Vec> = Vec::new(); 16 | dfs(&mut queens, &mut used_cols, &mut sum, &mut dif, n, &mut ret); 17 | ret 18 | } 19 | 20 | fn dfs( 21 | queens: &mut Vec, 22 | used_cols: &mut Vec, 23 | sum: &mut HashSet, 24 | dif: &mut HashSet, 25 | n: usize, 26 | ret: &mut Vec>, 27 | ) { 28 | if queens.len() == n { 29 | render_solution(queens, ret, n); 30 | return; 31 | } 32 | let x = queens.len() as i32; 33 | 34 | for y in 0..n { 35 | // 验证位置 36 | if used_cols[y] { 37 | continue; 38 | } 39 | let y_i32 = y as i32; 40 | let cur_sum = x + y_i32; 41 | if sum.contains(&cur_sum) { 42 | continue; 43 | } 44 | let cur_dif = x - y_i32; 45 | if dif.contains(&cur_dif) { 46 | continue; 47 | } 48 | 49 | // 搜索下一个位置 50 | sum.insert(cur_sum); 51 | dif.insert(cur_dif); 52 | used_cols[y] = true; 53 | queens.push(y_i32); 54 | dfs(queens, used_cols, sum, dif, n, ret); 55 | queens.pop().unwrap(); 56 | sum.remove(&cur_sum); 57 | dif.remove(&cur_dif); 58 | used_cols[y] = false; 59 | } 60 | } 61 | 62 | fn render_solution(queens: &[i32], res: &mut Vec>, n: usize) { 63 | let mut board: Vec = Vec::with_capacity(n); 64 | for i in 0..n { 65 | let mut row = vec!['.'.to_string(); n]; 66 | row[queens[i] as usize] = 'Q'.to_string(); 67 | board.push(row.join("")); 68 | } 69 | res.push(board); 70 | } 71 | -------------------------------------------------------------------------------- /src/bfs_dfs_backtracking/num_ways.rs: -------------------------------------------------------------------------------- 1 | //! https://leetcode-cn.com/problems/chuan-di-xin-xi/ 2 | fn num_ways(n: i32, relation: Vec>, k: i32) -> i32 { 3 | let mut graph = [[false; 10]; 10]; 4 | for each in relation { 5 | graph[each[0] as usize][each[1] as usize] = true; 6 | } 7 | 8 | let n = n as usize; 9 | let mut dfs_state = DfsState { 10 | ans: 0, 11 | n, 12 | destination: n - 1, 13 | max_steps: k as u8, 14 | graph, 15 | }; 16 | 17 | dfs_state.dfs(0, 0); 18 | dfs_state.ans 19 | } 20 | 21 | struct DfsState { 22 | ans: i32, 23 | n: usize, 24 | /// n-1 25 | destination: usize, 26 | max_steps: u8, 27 | graph: [[bool; 10]; 10], 28 | } 29 | 30 | impl DfsState { 31 | fn dfs(&mut self, pos: usize, step: u8) { 32 | if step == self.max_steps { 33 | if pos == self.destination { 34 | self.ans += 1; 35 | } 36 | return; 37 | } 38 | for next_pos in 0..self.n { 39 | if self.graph[pos][next_pos] { 40 | self.dfs(next_pos, step + 1); 41 | } 42 | } 43 | } 44 | } 45 | 46 | #[test] 47 | fn test_num_ways() { 48 | let test_cases = vec![ 49 | ( 50 | 5, 51 | vec_vec![[0, 2], [2, 1], [3, 4], [2, 3], [1, 4], [2, 0], [0, 4]], 52 | 3, 53 | 3, 54 | ), 55 | (3, vec_vec![[0, 2], [2, 1]], 2, 0), 56 | ]; 57 | for (n, relation, k, ans) in test_cases { 58 | assert_eq!(num_ways(n, relation, k), ans); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/bfs_dfs_backtracking/permutations.rs: -------------------------------------------------------------------------------- 1 | /// https://leetcode.com/problems/permutations/ 2 | /// https://leetcode.com/problems/permutations-ii/ 3 | struct PermutationsWithDedup { 4 | cur: Vec, 5 | ret: Vec>, 6 | nums: Vec, 7 | used: Vec, 8 | len: usize, 9 | } 10 | 11 | /// O(n! * n) 12 | impl PermutationsWithDedup { 13 | fn dfs(&mut self) { 14 | if self.cur.len() == self.len { 15 | // O(n) 16 | self.ret.push(self.cur.clone()); 17 | return; 18 | } 19 | 20 | for i in 0..self.len { 21 | if self.used[i] { 22 | continue; 23 | } 24 | 25 | /* 「剪枝去重」 26 | used[i-1]=false表示backtraing的过程中 nums[i-1] 已经被遍历过了 27 | 例如: [a1, a2, b] 28 | 搜索a1时一定考虑过 used[a1=true]+used[a2=false],的情况 29 | a2的搜索树遇到used[a2=false]时可以剪枝,因为跟搜索a1时重复了 30 | */ 31 | if i > 0 && self.nums[i - 1] == self.nums[i] && !self.used[i - 1] { 32 | continue; 33 | } 34 | 35 | self.used[i] = true; 36 | self.cur.push(self.nums[i]); 37 | self.dfs(); 38 | self.used[i] = false; 39 | self.cur.pop().unwrap(); 40 | } 41 | } 42 | } 43 | 44 | fn permutations_ii(mut nums: Vec) -> Vec> { 45 | nums.sort_unstable(); 46 | let len = nums.len(); 47 | let mut helper = PermutationsWithDedup { 48 | cur: Vec::new(), 49 | ret: Vec::new(), 50 | nums, 51 | used: vec![false; len], 52 | len, 53 | }; 54 | helper.dfs(); 55 | helper.ret 56 | } 57 | 58 | #[test] 59 | fn test_permutations_ii() { 60 | let test_cases = vec![ 61 | (vec![1, 1, 2], vec_vec![[1, 1, 2], [1, 2, 1], [2, 1, 1]]), 62 | ( 63 | vec![1, 2, 3], 64 | vec_vec![ 65 | [1, 2, 3], 66 | [1, 3, 2], 67 | [2, 1, 3], 68 | [2, 3, 1], 69 | [3, 1, 2], 70 | [3, 2, 1] 71 | ], 72 | ), 73 | ]; 74 | for (input, output) in test_cases { 75 | assert_eq!(permutations_ii(input), output); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/bfs_dfs_backtracking/restore_ip_address.rs: -------------------------------------------------------------------------------- 1 | /*! https://leetcode.com/problems/restore-ip-addresses/ 2 | 输入一个全是数字的字符串,用回溯算法去摆放3个IP地址的点分隔符,不是MySQL的InetAddr这样ip字符串和u32互转 3 | */ 4 | 5 | /// 方便回溯函数传参以及代码维护,传一个结构体等于传多个参数 6 | struct Args { 7 | s: Vec, 8 | s_len: usize, 9 | s_index: usize, 10 | /// 既然IP地址固定是4段,回溯算法可以用原生数组小技巧: 11 | /// 回溯过程中当前的已选择cur不需要push或pop,「新的选择会覆盖掉老的选择」,用原生数组提升性能 12 | segments: [u8; 4], 13 | segments_index: usize, 14 | res: Vec, 15 | } 16 | 17 | impl Args { 18 | fn new(s: String) -> Self { 19 | let s_len = s.len(); 20 | Self { 21 | s: s.into_bytes(), 22 | s_len, 23 | s_index: 0, 24 | segments: [0, 0, 0, 0], 25 | segments_index: 0, 26 | res: Vec::new(), 27 | } 28 | } 29 | 30 | fn add_new_addr_segments_answer_to_res(&mut self) { 31 | self.res.push(format!( 32 | "{}.{}.{}.{}", 33 | self.segments[0], self.segments[1], self.segments[2], self.segments[3] 34 | )); 35 | } 36 | 37 | fn swap_index(&mut self, new_s_index: usize, new_seg_index: usize) -> (usize, usize) { 38 | let old_index = (self.s_index, self.segments_index); 39 | self.s_index = new_s_index; 40 | self.segments_index = new_seg_index; 41 | old_index 42 | } 43 | 44 | fn restore_index(&mut self, index: (usize, usize)) { 45 | self.s_index = index.0; 46 | self.segments_index = index.1; 47 | } 48 | } 49 | 50 | fn restore_ip_addresses(s: String) -> Vec { 51 | fn helper(args: &mut Args) { 52 | if args.segments_index == 4 { 53 | if args.s_index == args.s_len { 54 | args.add_new_addr_segments_answer_to_res(); 55 | } 56 | return; 57 | } 58 | 59 | // 还没找全4段ip就遍历完字符串,那么字符串s一定不是合法IP地址,提前回溯 60 | if args.s_index == args.s_len { 61 | return; 62 | } 63 | 64 | // 由于leetcode的IP地址要求不能有前置0,所以遇到第一个0那么这段就一定是0 65 | if args.s[args.s_index] == b'0' { 66 | args.segments[args.segments_index] = 0; 67 | let backup_index = args.swap_index(args.s_index + 1, args.segments_index + 1); 68 | helper(args); 69 | args.restore_index(backup_index); 70 | } 71 | 72 | let mut ip_addr_seg = 0; 73 | for i in args.s_index..args.s_len { 74 | fn checked_addr_seg_add(old_val: u8, new_digit: u8) -> Option { 75 | old_val.checked_mul(10)?.checked_add(new_digit - b'0') 76 | } 77 | match checked_addr_seg_add(ip_addr_seg, args.s[i]) { 78 | Some(new_val) => { 79 | ip_addr_seg = new_val; 80 | if ip_addr_seg == 0_u8 { 81 | // 上一个if语句已经处理了IPv4为0的情况,这里就直接跳过 82 | // 直接break,避免0的情况被重复记入 83 | break; 84 | } 85 | args.segments[args.segments_index] = ip_addr_seg; 86 | let backup_index = args.swap_index(i + 1, args.segments_index + 1); 87 | helper(args); 88 | args.restore_index(backup_index); 89 | } 90 | None => return, 91 | } 92 | } 93 | } 94 | let mut args = Args::new(s); 95 | helper(&mut args); 96 | args.res 97 | } 98 | 99 | #[test] 100 | fn test_restore_ip_addresses() { 101 | let test_cases = vec![ 102 | ("0000", vec!["0.0.0.0".to_string()]), 103 | ( 104 | "25525511135", 105 | vec!["255.255.11.135".to_string(), "255.255.111.35".to_string()], 106 | ), 107 | ]; 108 | for (s, expected) in test_cases { 109 | assert_eq!(restore_ip_addresses(s.to_string()), expected); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/bfs_dfs_backtracking/the_maze.rs: -------------------------------------------------------------------------------- 1 | /// https://leetcode.com/problems/the-maze/ 2 | fn has_path(mut maze: Vec>, start: Vec, destination: Vec) -> bool { 3 | let (m, n) = (maze.len() as i32, maze[0].len() as i32); 4 | let mut queue = std::collections::VecDeque::new(); 5 | queue.push_back((start[0], start[1])); 6 | while let Some((x, y)) = queue.pop_front() { 7 | if x == destination[0] && y == destination[1] { 8 | return true; 9 | } 10 | // mark (x, y) as visited 11 | maze[x as usize][y as usize] = 2; 12 | for (dx, dy) in &[(1, 0), (-1, 0), (0, 1), (0, -1)] { 13 | let (mut x2, mut y2) = (x + dx, y + dy); 14 | // 必须沿着同一个方向走直到碰到障碍物或边界才能停下换方向,注意不能用`maze[x2][y2] == 0` 15 | while x2 >= 0 && x2 < m && y2 > 0 && y2 < n && maze[x2 as usize][y2 as usize] != 1 { 16 | // destructuring assignments are unstable 17 | // (x2, y2) = (x2 + dx, y2 + dy); 18 | x2 += dx; 19 | y2 += dy; 20 | } 21 | // 如果当前位置已经是边界或障碍物,则回退一格 22 | x2 -= dx; 23 | y2 -= dy; 24 | if maze[x2 as usize][y2 as usize] == 0 { 25 | // 如果停下来的位置没有走过 26 | queue.push_back((x2, y2)); 27 | } 28 | } 29 | } 30 | false 31 | } 32 | 33 | #[test] 34 | fn test_has_path() { 35 | let test_cases = vec![ 36 | ( 37 | vec_vec![ 38 | [0, 0, 1, 0, 0], 39 | [0, 0, 0, 0, 0], 40 | [0, 0, 0, 1, 0], 41 | [1, 1, 0, 1, 1], 42 | [0, 0, 0, 0, 0] 43 | ], 44 | vec![0, 4], 45 | vec![4, 4], 46 | true, 47 | ), 48 | ( 49 | vec_vec![ 50 | [0, 0, 1, 0, 0], 51 | [0, 0, 0, 0, 0], 52 | [0, 0, 0, 1, 0], 53 | [1, 1, 0, 1, 1], 54 | [0, 0, 0, 0, 0] 55 | ], 56 | vec![0, 4], 57 | vec![3, 2], 58 | false, 59 | ), 60 | ]; 61 | for (maze, start, destination, can_reach) in test_cases { 62 | assert_eq!(has_path(maze, start, destination), can_reach); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/binary_search/mountain_array.rs: -------------------------------------------------------------------------------- 1 | /// https://leetcode.com/problems/peak-index-in-a-mountain-array/ 2 | fn peak_index(nums: Vec) -> i32 { 3 | let mut left = 0; 4 | let mut right = nums.len() - 1; 5 | while left <= right { 6 | let mid = left + (right - left) / 2; 7 | if nums[mid] > nums[mid + 1] { 8 | if nums[mid] > nums[mid - 1] { 9 | return mid as i32; 10 | } 11 | right = mid; 12 | } else { 13 | left = mid; 14 | } 15 | } 16 | left as i32 17 | } 18 | 19 | fn peak_index_brute_force(nums: Vec) -> i32 { 20 | let mut i = 0; 21 | while nums[i] < nums[i + 1] { 22 | i += 1; 23 | } 24 | i as i32 25 | } 26 | 27 | fn peak_index_best(nums: Vec) -> i32 { 28 | let mut left = 0; 29 | let mut right = nums.len() - 1; 30 | while left < right { 31 | let mid = left + (right - left) / 2; 32 | if nums[mid] < nums[mid + 1] { 33 | left = mid + 1; 34 | } else { 35 | // mid索引不满足递增条件,所以山顶会在[left,mid]之间,但不能排除掉mid,mid可能是个peak,也可能是下坡的其中一个索引 36 | right = mid; 37 | } 38 | } 39 | left as i32 40 | } 41 | 42 | #[test] 43 | fn test_peak_index_in_a_mountain_array() { 44 | const TEST_CASES: [(&[i32], i32); 2] = [ 45 | (&[0, 2, 1, 0], 1), 46 | (&[24, 69, 100, 99, 79, 78, 67, 36, 26, 19], 2), 47 | ]; 48 | for (nums, peak_index_output) in TEST_CASES { 49 | assert_eq!(peak_index(nums.to_vec()), peak_index_output); 50 | assert_eq!(peak_index_brute_force(nums.to_vec()), peak_index_output); 51 | assert_eq!(peak_index_best(nums.to_vec()), peak_index_output); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/binary_search/search_a_2d_matrix.rs: -------------------------------------------------------------------------------- 1 | /// https://leetcode.com/problems/search-a-2d-matrix/ 2 | /// check target is_exists in a sorted 2d vector 3 | /// 既然二维数组是有序的,可以将其编号,这样就能看作是对普通数组进行二分,然后根据编号对n的余数和模又能得知编号在二维数组的坐标 4 | fn search_a_2d_matrix(matrix: Vec>, target: i32) -> bool { 5 | let (m, n) = (matrix.len(), matrix[0].len()); 6 | let (mut start, mut end) = (0, m * n - 1); 7 | while start <= end { 8 | // 注意这种二分搜索模板是start<=end,start==end时也要进行一次判断 9 | let mid = start + (end - start) / 2; 10 | // 注意i=mid/n而不是mid/m 11 | let (i, j) = (mid / n, mid % n); 12 | match matrix[i][j].cmp(&target) { 13 | std::cmp::Ordering::Less => start = mid + 1, 14 | std::cmp::Ordering::Equal => return true, 15 | std::cmp::Ordering::Greater => { 16 | // check end=mid-1 overflow 17 | if mid == 0 { 18 | return false; 19 | } 20 | end = mid - 1; 21 | } 22 | } 23 | } 24 | false 25 | } 26 | 27 | #[test] 28 | fn test_search_a_2d_matrix() { 29 | #[rustfmt::skip] 30 | let test_cases = vec![ 31 | (vec_vec![[1]], 2, false), 32 | (vec_vec![[1]], 0, false), 33 | (vec_vec![[1, 1]], 0, false), 34 | (vec_vec![[1, 3]], 1, true), 35 | ( 36 | vec_vec![[1, 3, 5, 7], 37 | [10, 11, 16, 20], 38 | [23, 30, 34, 60]], 39 | 3, 40 | true, 41 | ), 42 | ( 43 | vec_vec![[1, 3, 5, 7], 44 | [10, 11, 16, 20], 45 | [23, 30, 34, 60]], 46 | 13, 47 | false, 48 | ), 49 | ]; 50 | for (matrix, target, matrix_contains_target) in test_cases { 51 | assert_eq!(search_a_2d_matrix(matrix, target), matrix_contains_target); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/binary_search/single_element_in_a_sorted_array.rs: -------------------------------------------------------------------------------- 1 | /// https://leetcode.com/problems/single-element-in-a-sorted-array 2 | #[allow(clippy::collapsible_else_if)] 3 | fn single_non_duplicate(nums: Vec) -> i32 { 4 | let mut left = 0; 5 | let mut right = nums.len() - 1; 6 | while left < right { 7 | let mid = left + (right - left) / 2; 8 | if mid % 2 == 0 { 9 | if nums[mid] == nums[mid + 1] { 10 | left = mid + 1; 11 | } else { 12 | right = mid; 13 | } 14 | } else { 15 | if nums[mid] == nums[mid - 1] { 16 | left = mid + 1; 17 | } else { 18 | right = mid; 19 | } 20 | } 21 | } 22 | nums[left] 23 | } 24 | 25 | #[test] 26 | fn test_single_non_duplicate() { 27 | assert_eq!(single_non_duplicate(vec![3, 3, 7, 7, 10, 11, 11]), 10); 28 | } 29 | -------------------------------------------------------------------------------- /src/binary_tree/evaluate_boolean_binary_tree.rs: -------------------------------------------------------------------------------- 1 | use super::prelude::*; 2 | 3 | /// https://leetcode.cn/problems/evaluate-boolean-binary-tree/ 4 | fn evaluate_tree(root: Option>>) -> bool { 5 | let root = root.unwrap(); 6 | let root = root.borrow(); 7 | if root.left.is_none() && root.right.is_none() { 8 | return root.val == 1; 9 | } 10 | let left = evaluate_tree(root.left.clone()); 11 | let right = evaluate_tree(root.right.clone()); 12 | if root.val == 2 { 13 | left || right 14 | } else { 15 | left && right 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/binary_tree/invert_binary_tree.rs: -------------------------------------------------------------------------------- 1 | use super::prelude::*; 2 | 3 | /** 4 | ```python 5 | def invert_binary_tree(root: TreeNode) -> TreeNode: 6 | if root is None: 7 | return root 8 | root.left, root.right = root.right, root.left 9 | invert_binary_tree(root.left) 10 | invert_binary_tree(root.right) 11 | return root 12 | ``` 13 | */ 14 | fn invert_tree(mut root: Option>>) -> Option>> { 15 | /// 应当新建一个新的函数去递归,因为二叉树式In-Place原地修改,所以用解答的函数递归的返回值是多余且降低性能的 16 | fn invert(root: &mut TreeNode) { 17 | let left = &mut root.left; 18 | let right = &mut root.right; 19 | std::mem::swap(left, right); 20 | if let Some(node) = left { 21 | invert(&mut node.borrow_mut()); 22 | } 23 | if let Some(node) = right { 24 | invert(&mut node.borrow_mut()); 25 | } 26 | } 27 | invert(&mut root.as_mut()?.borrow_mut()); 28 | root 29 | } 30 | 31 | fn invert_tree_unsafe_solution( 32 | mut root: Option>>, 33 | ) -> Option>> { 34 | let root_ptr = root.as_mut()?.as_ptr(); 35 | unsafe { 36 | let left = &mut (*root_ptr).left; 37 | let right = &mut (*root_ptr).right; 38 | std::mem::swap(left, right); 39 | invert_tree_unsafe_solution(left.clone()); 40 | invert_tree_unsafe_solution(right.clone()); 41 | } 42 | root 43 | } 44 | -------------------------------------------------------------------------------- /src/binary_tree/is_bst.rs: -------------------------------------------------------------------------------- 1 | //! https://leetcode.com/problems/validate-binary-search-tree/ 2 | 3 | use super::prelude::*; 4 | 5 | #[allow(clippy::option_if_let_else)] 6 | fn is_bst(node: Option>>, lower: Option, upper: Option) -> bool { 7 | match node { 8 | Some(node) => { 9 | let node = node.borrow(); 10 | let node_val = node.val; 11 | lower.map_or(true, |lower| lower < node_val) 12 | && upper.map_or(true, |upper| node_val < upper) 13 | && is_bst(node.left.clone(), lower, Some(node_val)) 14 | && is_bst(node.right.clone(), Some(node_val), upper) 15 | } 16 | None => true, 17 | } 18 | } 19 | 20 | static mut PRE_ORDER_PREV_VAL: Option = None; 21 | 22 | /** https://leetcode.com/problems/validate-binary-search-tree/ 23 | 先序遍历二叉树,记录上一个先序遍历的值与当前值比较,如果curr.val<=prev.val(非升序),则不是bst 24 | 该解法用到了static全局变量,属于有状态的函数,调用结束后会static全局变量的值残留上次调用的结果 25 | ```python 26 | class Solution: 27 | def is_bst(self, root: TreeNode) -> bool: 28 | self.prev_val = float('-inf') 29 | 30 | def is_not_bst(node: TreeNode): 31 | if node is None: 32 | return False 33 | if is_not_bst(node.left): 34 | return True 35 | if self.prev_val >= node.val: 36 | return True 37 | # 离开当前层递归时即将进入右子树递归时,更新上一层递归的节点的值(self.prev_val) 38 | self.prev_val = node.val 39 | if is_not_bst(node.right): 40 | return True 41 | return False 42 | return not is_not_bst(root) 43 | ``` 44 | */ 45 | fn is_bst_dirty_solution(root: Option>>) -> bool { 46 | match root { 47 | Some(root) => unsafe { 48 | let root = root.borrow(); 49 | if !is_bst_dirty_solution(root.left.clone()) { 50 | return false; 51 | } 52 | 53 | if PRE_ORDER_PREV_VAL.map_or(false, |prev_val| root.val <= prev_val) { 54 | return false; 55 | } 56 | // if let Some(prev_node_val) = PRE_ORDER_PREV_VAL { 57 | // if root.val <= prev_node_val { 58 | // return false; 59 | // } 60 | // } 61 | 62 | PRE_ORDER_PREV_VAL = Some(root.val); 63 | if !is_bst_dirty_solution(root.right.clone()) { 64 | return false; 65 | } 66 | true 67 | }, 68 | None => true, 69 | } 70 | } 71 | 72 | fn is_valid_bst(root: Option>>) -> bool { 73 | // 由于该solution用到了static是有状态的,所以每次运行前需要重置static全局变量,否则static变量就是上次运行的残留结果 74 | unsafe { 75 | PRE_ORDER_PREV_VAL = None; 76 | } 77 | is_bst_dirty_solution(root) 78 | } 79 | -------------------------------------------------------------------------------- /src/binary_tree/leaf_similar_trees.rs: -------------------------------------------------------------------------------- 1 | //! https://leetcode.com/problems/leaf-similar-trees/ 2 | //! 事实上这题 无论用前中后序遍历二叉树得到叶子节点 3 | use super::prelude::*; 4 | 5 | fn reverse_post_order(node: TreeLink) -> Vec { 6 | let mut ret = vec![]; 7 | let mut stack = vec![node]; 8 | while let Some(Some(node)) = stack.pop() { 9 | let node = node.borrow(); 10 | // `根右左`「逆」后序遍历 (后序遍历=`左右根`) 11 | if node.left.is_none() && node.right.is_none() { 12 | ret.push(node.val); 13 | } 14 | stack.push(node.left.clone()); 15 | stack.push(node.right.clone()); 16 | } 17 | ret 18 | } 19 | 20 | fn leaf_similar_trees(root1: TreeLink, root2: TreeLink) -> bool { 21 | let leafs1 = reverse_post_order(root1); 22 | let leafs2 = reverse_post_order(root2); 23 | leafs1 == leafs2 24 | } 25 | 26 | #[test] 27 | fn test_leaf_similar_trees() { 28 | const TEST_CASES: [(&[i32], &[i32], bool); 3] = [ 29 | ( 30 | &[3, 5, 1, 6, 2, 9, 8, null, null, 7, 4], 31 | &[3, 5, 1, 6, 2, 9, 8, null, null, 7, 4], 32 | true, 33 | ), 34 | (&[1], &[1], true), 35 | (&[1], &[2], false), 36 | ]; 37 | for (root1, root2, is_leaf_similar) in TEST_CASES { 38 | let root1 = deserialize_vec_to_binary_tree(root1); 39 | let root2 = deserialize_vec_to_binary_tree(root2); 40 | assert_eq!(leaf_similar_trees(root1, root2), is_leaf_similar); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/binary_tree/max_depth_of_binary_tree.rs: -------------------------------------------------------------------------------- 1 | use super::TreeLink; 2 | 3 | /// https://leetcode.com/problems/maximum-depth-of-binary-tree/ 4 | #[allow(clippy::option_if_let_else)] 5 | fn max_depth(root: &TreeLink) -> usize { 6 | match root { 7 | Some(node) => { 8 | let node = node.borrow(); 9 | max_depth(&node.left).max(max_depth(&node.right)) + 1 10 | } 11 | None => 0, 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/binary_tree/merge_two_binary_trees.rs: -------------------------------------------------------------------------------- 1 | use super::prelude::*; 2 | 3 | fn merge_two_binary_trees(root1: TreeLink, root2: TreeLink) -> TreeLink { 4 | if root1.is_none() { 5 | return root2; 6 | } 7 | if root2.is_none() { 8 | return root1; 9 | } 10 | 11 | let node1 = root1.clone()?; 12 | let mut node1 = node1.borrow_mut(); 13 | let node2 = root2?; 14 | let node2 = node2.borrow(); 15 | 16 | node1.val += node2.val; 17 | node1.left = merge_two_binary_trees(node1.left.clone(), node2.left.clone()); 18 | node1.right = merge_two_binary_trees(node1.right.clone(), node2.right.clone()); 19 | 20 | root1 21 | } 22 | 23 | #[test] 24 | fn test_merge_two_binary_trees() { 25 | const TEST_CASES: [(&[i32], &[i32], &[i32]); 1] = [( 26 | &[1, 3, 2, 5], 27 | &[2, 1, 3, null, 4, null, 7], 28 | &[3, 4, 5, 5, 4, null, 7], 29 | )]; 30 | for (root1, root2, output) in TEST_CASES { 31 | let root1 = deserialize_vec_to_binary_tree(root1); 32 | let root2 = deserialize_vec_to_binary_tree(root2); 33 | let output_binary_tree = deserialize_vec_to_binary_tree(output); 34 | println!("root1 = "); 35 | print_binary_tree(root1.clone()).unwrap(); 36 | println!("root2 = "); 37 | print_binary_tree(root2.clone()).unwrap(); 38 | println!("output = "); 39 | print_binary_tree(output_binary_tree).unwrap(); 40 | assert_eq!( 41 | serialize_binary_tree_to_vec(merge_two_binary_trees(root1, root2)), 42 | output 43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/binary_tree/mod.rs: -------------------------------------------------------------------------------- 1 | mod evaluate_boolean_binary_tree; 2 | #[cfg(feature = "rustc_private")] 3 | mod graphviz_view_leetcode_binary_tree; 4 | mod invert_binary_tree; 5 | mod is_bst; 6 | mod leaf_similar_trees; 7 | mod level_order_traversal; 8 | mod max_depth_of_binary_tree; 9 | mod merge_two_binary_trees; 10 | mod preorder_traversal; 11 | mod same_tree; 12 | mod search_val_or_range_in_bst; 13 | mod serde_binary_tree_to_leetcode_vec; 14 | mod serde_binary_tree_to_parentheses_str; 15 | mod std_ops_controlflow_in_binary_tree; 16 | mod sum_of_left_leaves; 17 | mod sum_root_to_leaf_numbers; 18 | mod univalued_binary_tree; 19 | 20 | pub mod prelude { 21 | pub use super::TreeLink; 22 | pub use super::TreeNode; 23 | pub use std::cell::RefCell; 24 | pub use std::rc::Rc; 25 | 26 | // if cfg test 27 | pub use super::null; 28 | pub use super::serde_binary_tree_to_leetcode_vec::{ 29 | deserialize_vec_to_binary_tree, print_binary_tree, serialize_binary_tree_to_vec, 30 | }; 31 | } 32 | 33 | #[allow(non_upper_case_globals)] 34 | pub const null: i32 = i32::MIN; 35 | 36 | /// 正常的二叉树的节点也不可能有两个父亲,所以leetcode用Rc真是多余 37 | /// 我做过那么多题也没见过二叉树节点的左右儿子是同一个节点 38 | /// https://github.com/pretzelhammer/rust-blog/blob/master/posts/learning-rust-in-2020.md#leetcode 39 | /// Option>> is overkill for tree links 40 | /// Rust的Debug可以递归打印出二叉树,比我用Python写的打印二叉树更准更好,约等于leetcode的Python二叉树的__repr__()的效果 41 | #[derive(Debug, PartialEq, Eq)] 42 | pub struct TreeNode { 43 | val: i32, 44 | left: TreeLink, 45 | right: TreeLink, 46 | } 47 | 48 | pub type TreeLink = Option>>; 49 | 50 | impl TreeNode { 51 | #[inline] 52 | pub const fn new(val: i32) -> Self { 53 | Self { 54 | val, 55 | left: None, 56 | right: None, 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/binary_tree/preorder_traversal.rs: -------------------------------------------------------------------------------- 1 | use super::prelude::*; 2 | 3 | /* reproduce cannot borrow `stack` as mutable because it is also borrowed as immutable 4 | // HashMap的Entry的出现为了解决所有权内部元素可变的限制,例如counter中想同时通过插入元素修改HashMap本身,还同时修改HashMap内部的某个值,可能会报错,Entry的出现就是为了解决此问题 5 | fn preorder_traversal_mut_borrow_err(root: Option>>) -> Vec { 6 | let mut res = Vec::new(); 7 | let mut stack = vec![root]; 8 | // immutable borrow occurs here 9 | while let Some(peek) = stack.last() { 10 | if let Some(peek) = peek { 11 | let peek = peek.borrow(); 12 | res.push(peek.val); 13 | // mutable borrow occurs here 14 | stack.push(peek.right.clone()); 15 | stack.push(peek.left.clone()); 16 | } 17 | } 18 | res 19 | } 20 | */ 21 | 22 | /** 23 | 递归版preorder遍历太简单了,就不写了 24 | 栈遍历的升级版是: 「莫里斯遍历+线索二叉树」 25 | 莫尼斯遍历不需要借助队列或栈的空间,与前序遍历不同的是莫里斯遍历每个节点只会访问一次 26 | */ 27 | fn preorder_traversal(root: Option>>) -> Vec { 28 | let mut ret = Vec::new(); 29 | let mut stack = vec![root]; 30 | // 不能用 Some(Some(peek)) 会把 None 情况忽略掉 31 | #[allow(clippy::collapsible_match)] 32 | while let Some(peek) = stack.pop() { 33 | if let Some(peek) = peek { 34 | let peek = peek.borrow(); 35 | ret.push(peek.val); 36 | stack.push(peek.right.clone()); 37 | stack.push(peek.left.clone()); 38 | } 39 | } 40 | ret 41 | } 42 | 43 | fn preorder_traversal_2(root: Option>>) -> Vec { 44 | fn helper(ret: &mut Vec, node: Option>>) -> Option<()> { 45 | let mut stack = vec![node]; 46 | while let Some(peek) = stack.pop()? { 47 | let peek = peek.borrow(); 48 | ret.push(peek.val); 49 | stack.push(peek.right.clone()); 50 | stack.push(peek.left.clone()); 51 | } 52 | Some(()) 53 | } 54 | let mut ret = Vec::new(); 55 | helper(&mut ret, root).unwrap_or_default(); 56 | ret 57 | } 58 | 59 | #[test] 60 | fn test_preorder_traversal() { 61 | let root = 62 | super::serde_binary_tree_to_parentheses_str::parentheses_str_to_binary_tree("1()(2(3))"); 63 | assert_eq!(preorder_traversal(root), vec![1, 2, 3]); 64 | } 65 | -------------------------------------------------------------------------------- /src/binary_tree/same_tree.rs: -------------------------------------------------------------------------------- 1 | use super::prelude::*; 2 | 3 | /// https://leetcode.com/problems/same-tree/ 4 | fn is_same_tree(p: Option>>, q: Option>>) -> bool { 5 | // p == q 6 | match (p, q) { 7 | (None, None) => true, 8 | (Some(p), Some(q)) => { 9 | let (p, q) = (p.borrow(), q.borrow()); 10 | p.val == q.val 11 | && is_same_tree(p.left.clone(), q.left.clone()) 12 | && is_same_tree(p.right.clone(), q.right.clone()) 13 | } 14 | (_, _) => false, 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/binary_tree/search_val_or_range_in_bst.rs: -------------------------------------------------------------------------------- 1 | use super::prelude::*; 2 | 3 | fn search_val_in_bst( 4 | root: Option>>, 5 | val: i32, 6 | ) -> Option>> { 7 | let mut cur = root; 8 | while let Some(node_outer) = cur { 9 | let node = node_outer.borrow(); 10 | match node.val.cmp(&val) { 11 | std::cmp::Ordering::Less => cur = node.right.clone(), 12 | std::cmp::Ordering::Greater => cur = node.left.clone(), 13 | std::cmp::Ordering::Equal => { 14 | drop(node); 15 | return Some(node_outer); 16 | } 17 | } 18 | } 19 | None 20 | } 21 | 22 | /// https://leetcode.com/problems/range-sum-of-bst/ 23 | fn range_sum_bst(root: Option>>, low: i32, high: i32) -> i32 { 24 | let mut res = 0; 25 | let mut stack = vec![root]; 26 | while let Some(Some(peek)) = stack.pop() { 27 | let peek = peek.borrow(); 28 | 29 | if low <= peek.val && peek.val <= high { 30 | res += peek.val; 31 | } 32 | stack.push(peek.right.clone()); 33 | stack.push(peek.left.clone()); 34 | } 35 | res 36 | } 37 | -------------------------------------------------------------------------------- /src/binary_tree/serde_binary_tree_to_parentheses_str.rs: -------------------------------------------------------------------------------- 1 | use super::prelude::*; 2 | 3 | /// TODO add tree_node to str function 4 | pub(super) fn parentheses_str_to_binary_tree(s: &str) -> TreeLink { 5 | let mut stack: Vec>> = Vec::new(); 6 | let mut val_len = 0; 7 | let mut is_left_subtree_empty = false; 8 | let s = s.as_bytes(); 9 | for i in 0..s.len() { 10 | if s[i] != b'(' && s[i] != b')' { 11 | val_len += 1; 12 | continue; 13 | } 14 | if val_len > 0 { 15 | let node_val = String::from_utf8(s[i - val_len..i].to_owned()) 16 | .unwrap() 17 | .parse::() 18 | .unwrap(); 19 | let node = Rc::new(RefCell::new(TreeNode::new(node_val))); 20 | if let Some(peek) = stack.last_mut() { 21 | let mut peek = peek.borrow_mut(); 22 | if is_left_subtree_empty { 23 | peek.right = Some(node.clone()); 24 | is_left_subtree_empty = false; 25 | } else if peek.left.is_none() { 26 | peek.left = Some(node.clone()); 27 | } else { 28 | peek.right = Some(node.clone()); 29 | } 30 | } 31 | stack.push(node.clone()); 32 | val_len = 0; 33 | } 34 | if s[i] == b')' { 35 | if s[i - 1] == b'(' { 36 | is_left_subtree_empty = true; 37 | } else { 38 | stack.pop().unwrap(); 39 | } 40 | } 41 | } 42 | stack.last().cloned() 43 | } 44 | 45 | #[test] 46 | fn test_str_to_optional_tree_node() { 47 | let node = parentheses_str_to_binary_tree("1()(2(3))"); 48 | print_binary_tree(node).unwrap(); 49 | } 50 | -------------------------------------------------------------------------------- /src/binary_tree/std_ops_controlflow_in_binary_tree.rs: -------------------------------------------------------------------------------- 1 | //! WIP 2 | use std::ops::ControlFlow; 3 | 4 | struct TreeNode { 5 | value: T, 6 | left: Option>>, 7 | right: Option>>, 8 | } 9 | 10 | /** 11 | ```text 12 | 3 13 | / \ 14 | 9 20 15 | / \ 16 | 15 7 17 | ``` 18 | */ 19 | fn tree_example() -> TreeNode { 20 | TreeNode { 21 | value: 3, 22 | left: TreeNode::new(9), 23 | right: Some(Box::new(TreeNode { 24 | value: 20, 25 | left: TreeNode::new(15), 26 | right: TreeNode::new(7), 27 | })), 28 | } 29 | } 30 | 31 | impl TreeNode { 32 | #[allow(clippy::unnecessary_wraps)] 33 | fn new(value: T) -> Option> { 34 | Some(Box::new(Self { 35 | value, 36 | left: None, 37 | right: None, 38 | })) 39 | } 40 | } 41 | 42 | /// Working In Progress 43 | fn traverse_inorder(root: Option>>) -> ControlFlow, Vec> { 44 | match root { 45 | Some(node) => { 46 | let mut return_val = vec![]; 47 | return_val.extend(traverse_inorder(node.left)?); 48 | return_val.extend(traverse_inorder(node.right)?); 49 | ControlFlow::Break(return_val) 50 | } 51 | None => ControlFlow::Continue(Vec::new()), 52 | } 53 | } 54 | 55 | #[test] 56 | fn test_traverse_inorder() { 57 | let tree = tree_example(); 58 | let a = traverse_inorder(Some(Box::new(tree))) 59 | .break_value() 60 | .unwrap(); 61 | dbg!(a); 62 | } 63 | -------------------------------------------------------------------------------- /src/binary_tree/sum_of_left_leaves.rs: -------------------------------------------------------------------------------- 1 | use super::prelude::*; 2 | 3 | fn sum_of_left_leaves(root: Option>>) -> i32 { 4 | helper(root, false) 5 | } 6 | 7 | fn helper(node: Option>>, is_left: bool) -> i32 { 8 | match node { 9 | Some(node) => { 10 | let node = node.borrow(); 11 | if node.left.is_none() && node.right.is_none() { 12 | if is_left { 13 | return node.val; 14 | } 15 | return 0; 16 | } 17 | helper(node.left.clone(), true) + helper(node.right.clone(), false) 18 | } 19 | None => 0, 20 | } 21 | } 22 | 23 | #[test] 24 | fn test_sum_of_left_leaves() { 25 | const TEST_CASES: [(&[i32], i32); 1] = [(&[3, 9, 20, null, null, 15, 7], 24)]; 26 | for (input, output) in TEST_CASES { 27 | let input = deserialize_vec_to_binary_tree(input); 28 | assert_eq!(sum_of_left_leaves(input), output); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/binary_tree/sum_root_to_leaf_numbers.rs: -------------------------------------------------------------------------------- 1 | use super::prelude::*; 2 | 3 | struct Solution; 4 | 5 | impl Solution { 6 | fn sum_numbers(root: Option>>) -> i32 { 7 | fn dfs_backtracking( 8 | node: Option>>, 9 | curr_path_sum: &mut i32, 10 | sum: &mut i32, 11 | ) { 12 | if let Some(node) = node { 13 | let node = node.borrow(); 14 | let old_curr_path_sum = *curr_path_sum; 15 | *curr_path_sum = *curr_path_sum * 10 + node.val; 16 | 17 | // found leaf node 18 | if node.left.is_none() && node.right.is_none() { 19 | // dbg!(*curr_path_sum, *sum); 20 | *sum += *curr_path_sum; 21 | } else { 22 | // dfs search 23 | if node.left.is_some() { 24 | dfs_backtracking(node.left.clone(), curr_path_sum, sum); 25 | } 26 | if node.right.is_some() { 27 | dfs_backtracking(node.right.clone(), curr_path_sum, sum); 28 | } 29 | } 30 | 31 | // leave current recursive bfs_dfs_backtracking 32 | *curr_path_sum = old_curr_path_sum; 33 | } 34 | } 35 | let mut curr_path_sum = 0; 36 | let mut sum = 0; 37 | dfs_backtracking(root, &mut curr_path_sum, &mut sum); 38 | sum 39 | } 40 | 41 | fn sum_numbers_best(root: Option>>) -> i32 { 42 | fn dfs_backtracking_best( 43 | node: Option>>, 44 | curr_path_sum: &mut i32, 45 | ) -> i32 { 46 | let node = node.unwrap(); 47 | let node = node.borrow(); 48 | let old_curr_path_sum = *curr_path_sum; 49 | *curr_path_sum = *curr_path_sum * 10 + node.val; 50 | let mut sum = 0; 51 | if node.left.is_none() && node.right.is_none() { 52 | sum += *curr_path_sum; 53 | } else { 54 | if node.left.is_some() { 55 | sum += dfs_backtracking_best(node.left.clone(), curr_path_sum); 56 | } 57 | if node.right.is_some() { 58 | sum += dfs_backtracking_best(node.right.clone(), curr_path_sum); 59 | } 60 | } 61 | // leave current recursive bfs_dfs_backtracking 62 | *curr_path_sum = old_curr_path_sum; 63 | sum 64 | } 65 | 66 | if root.is_none() { 67 | return 0; 68 | } 69 | dfs_backtracking_best(root, &mut 0_i32) 70 | } 71 | } 72 | 73 | #[test] 74 | fn test_preorder_traversal() { 75 | // 4->9->5, 4->9->1, 4->0: 495+491+40=1026 76 | let root = super::serde_binary_tree_to_parentheses_str::parentheses_str_to_binary_tree( 77 | "4(9(5)(1))(0)", 78 | ); 79 | assert_eq!(Solution::sum_numbers(root.clone()), 1026); 80 | assert_eq!(Solution::sum_numbers_best(root), 1026); 81 | } 82 | -------------------------------------------------------------------------------- /src/binary_tree/univalued_binary_tree.rs: -------------------------------------------------------------------------------- 1 | //! https://leetcode.com/problems/univalued-binary-tree/ 2 | use super::prelude::*; 3 | 4 | fn is_unival_tree(root: Option>>) -> bool { 5 | match root { 6 | Some(root) => { 7 | let root = root.borrow(); 8 | 9 | if !is_unival_tree(root.left.clone()) { 10 | return false; 11 | } 12 | if !is_unival_tree(root.right.clone()) { 13 | return false; 14 | } 15 | 16 | if let Some(ref child) = root.left { 17 | if root.val != child.borrow().val { 18 | return false; 19 | } 20 | } 21 | if let Some(ref child) = root.right { 22 | if root.val != child.borrow().val { 23 | return false; 24 | } 25 | } 26 | 27 | true 28 | } 29 | None => true, 30 | } 31 | } 32 | 33 | #[test] 34 | fn test_is_unival_tree() { 35 | for (tree, is_unival) in [ 36 | (vec![1, 1, 1, 1, 1, null, 1], true), 37 | (vec![2, 2, 2, 5, 2], false), 38 | ] { 39 | assert_eq!( 40 | is_unival_tree(deserialize_vec_to_binary_tree(&tree)), 41 | is_unival 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/bitwise/find_single_number.rs: -------------------------------------------------------------------------------- 1 | /* 2 | |136|[Single Number](https://leetcode.com/problems/single-number/) 3 | |137|[Single Number II](https://leetcode.com/problems/single-number-ii/) 4 | |260|[Single Number III](https://leetcode.com/problems/single-number-iii/)| 5 | |389|[Find The Difference](https://leetcode.com/problems/find-the-difference/) 6 | */ 7 | 8 | /// nums是一个长度为2n+1的的数组,有其中一个元素出现了一次,其余元素都是出现了两次 9 | /// 利用数学运算规律: (sum_all_other+single)*2 - (sum_all_other*2+single) = single 10 | fn single_number_sum_solution(nums: Vec) -> i32 { 11 | let nums_set: std::collections::HashSet = nums.clone().into_iter().collect(); 12 | nums_set.into_iter().sum::() * 2 - nums.into_iter().sum::() 13 | } 14 | 15 | /// 利用异或规律: A^B^A = A^A^B = 0^B = B 16 | fn single_number_xor_solution(nums: Vec) -> i32 { 17 | // leetcode的Rust版本太低,还没有bitxor API(或者需要`use std::ops::BitXor;`) 18 | // nums.into_iter().fold(0, |a, b| a.bitxor(b)) 19 | nums.into_iter().fold(0, |a, b| a ^ b) 20 | } 21 | 22 | /// single_number_2的bitwise解法要用状态机去理解,不方便背诵 23 | fn single_number_2_sum_solution(nums: Vec) -> i32 { 24 | let nums_set: std::collections::HashSet = 25 | nums.clone().into_iter().map(i64::from).collect(); 26 | let nums_set_sum = nums_set.into_iter().sum::(); 27 | let nums_sum = nums.into_iter().map(i64::from).sum::(); 28 | ((nums_set_sum * 3 - nums_sum) / 2) as i32 29 | } 30 | 31 | fn single_number_3_counter_solution(nums: Vec) -> Vec { 32 | let mut counter = std::collections::HashMap::with_capacity(nums.len()); 33 | for num in nums { 34 | *counter.entry(num).or_insert(0_u16) += 1; 35 | } 36 | let mut ret = Vec::with_capacity(2); 37 | for (num, count) in counter { 38 | if count == 1 { 39 | ret.push(num); 40 | if ret.len() == 2 { 41 | return ret; 42 | } 43 | } 44 | } 45 | unreachable!() 46 | } 47 | 48 | fn find_the_difference_counter_solution(s: String, t: String) -> char { 49 | let mut counter = [0_u16; 26]; 50 | for each in s.into_bytes() { 51 | counter[(each - b'a') as usize] += 1; 52 | } 53 | for each in t.into_bytes() { 54 | let idx = (each - b'a') as usize; 55 | match counter[idx].checked_sub(1) { 56 | Some(new_val) => counter[idx] = new_val, 57 | None => return each as char, 58 | } 59 | } 60 | for (i, each) in counter.iter().enumerate() { 61 | if each.eq(&1) { 62 | return (i as u8 + b'a') as char; 63 | } 64 | } 65 | unreachable!() 66 | } 67 | 68 | fn find_the_difference_sum_solution(s: String, t: String) -> char { 69 | (t.into_bytes().into_iter().sum::() - s.into_bytes().into_iter().sum::()) as char 70 | } 71 | 72 | fn find_the_difference_xor_solution(s: String, t: String) -> char { 73 | s.into_bytes() 74 | .into_iter() 75 | .chain(t.into_bytes().into_iter()) 76 | .fold(0, |a, b| a ^ b) 77 | .into() 78 | } 79 | -------------------------------------------------------------------------------- /src/bitwise/gray_code.rs: -------------------------------------------------------------------------------- 1 | /** 2 | 《数字信号处理》—— 镜像反射法生成格雷码: 3 | 0 1 2 4 | 0 0 00 5 | 1 01 6 | 11 7 | 10 8 | 9 | 先将上次迭代的结果「上下镜像复制」一份,原顺序部分在左侧加上0,镜像对称部分在左侧加上1 10 | 这样能保证格雷码的要求: 从上到下仅有1bit的变化 11 | 如果懂了这些背景知识,那么这题就是easy难度了,首先左侧补0的那一半可以不做(二进制左侧加0等于不变),因为原数组各项+0后还是原数组 12 | 13 | ```python 14 | def gray_code(n: int) -> List[int]: 15 | # 镜像反射法生成格雷码的技巧,左侧要补1的镜像部分的个数刚好等于head,而且 16 | res, head = [0], 1 17 | for _ in range(n): 18 | for i in range(head - 1, -1, -1): 19 | res.append(head + res[i]) 20 | head *= 2 21 | return res 22 | ``` 23 | */ 24 | fn gray_code(n: i32) -> Vec { 25 | let mut res: Vec = Vec::with_capacity(2_usize.pow(n as u32)); 26 | res.push(0); 27 | // head表示镜像反射法生成格雷码中左侧补1需要加上的数,刚好等于镜像后的下半部分(需要左边加一个1)的个数 28 | let mut head: i32 = 1; 29 | for _ in 0..n { 30 | // 镜像对称部分在左侧加上1 31 | for i in (0..head as usize).rev() { 32 | res.push(head + res[i]); 33 | } 34 | head <<= 1; 35 | } 36 | res 37 | /* 位运算的解法? 38 | let mut res=vec![]; 39 | for i in 0..1<>1); 41 | } 42 | res 43 | */ 44 | } 45 | 46 | #[test] 47 | fn test_gray_code() { 48 | assert_eq!(gray_code(2), vec![0b00, 0b01, 0b11, 0b10]); 49 | } 50 | -------------------------------------------------------------------------------- /src/bitwise/hamming_distance_count_ones.rs: -------------------------------------------------------------------------------- 1 | /// https://leetcode.com/problems/number-of-1-bits/ 2 | mod hamming_weight { 3 | /// number_of_1_bits: n.count_ones(); // Java: Integer.bitCount(n) 4 | const fn hamming_weight_count_ones_solution(n: u32) -> i32 { 5 | n.count_ones() as i32 6 | } 7 | 8 | /// 用汇编的popcnt指令去统计正整数里有几个1 9 | /// __builtin_popcount in c++ 10 | #[allow(clippy::inline_asm_x86_intel_syntax)] 11 | fn hamming_weight_asm_popcnt_solution(n: u32) -> i32 { 12 | let popcnt_input: usize = n as usize; 13 | let popcnt_output: usize; 14 | unsafe { 15 | std::arch::asm!( 16 | "popcnt {popcnt_output}, {popcnt_input}", 17 | popcnt_input = in(reg) popcnt_input, 18 | popcnt_output = out(reg) popcnt_output, 19 | ); 20 | } 21 | popcnt_output as i32 22 | } 23 | 24 | /// count_ones最容易理解和记忆的位运算解法 25 | fn hamming_weight_bitwise_solution_1(n: u32) -> i32 { 26 | let mut count = 0; 27 | let mut mask = 0b1; 28 | for _ in 0..32 { 29 | if n & mask == 1 { 30 | count += 1; 31 | } 32 | mask <<= 1; 33 | } 34 | count 35 | } 36 | 37 | /// 利用`is_power_of_2`一题的相同的位运算技巧取出从右往左的第一个1 38 | const fn hamming_weight_bitwise_solution_2(mut n: u32) -> i32 { 39 | let mut count = 0; 40 | while n != 0 { 41 | n &= n - 1; 42 | count += 1; 43 | } 44 | count 45 | } 46 | } 47 | 48 | /// https://leetcode.com/problems/hamming-distance/ 49 | /// 汉明距离: 两个整数的二进制表示不同位置的个数 50 | /// 思路: 异或后(不同位得1) count_ones 51 | const fn hamming_distance(x: i32, y: i32) -> i32 { 52 | (x ^ y).count_ones() as i32 53 | } 54 | 55 | /// https://leetcode.com/problems/total-hamming-distance/ 56 | /// O(n^2) 的遍历数组中的两两组合在 leetcode 用大部分语言提交都会超时 57 | fn total_hamming_distance_brute_force(nums: Vec) -> i32 { 58 | let mut ret = 0; 59 | let len = nums.len(); 60 | for i in 0..len { 61 | for j in i + 1..len { 62 | ret += (nums[i] ^ nums[j]).count_ones(); 63 | } 64 | } 65 | ret as i32 66 | } 67 | 68 | /// 大约 O(n) 时间复杂度 69 | fn total_hamming_distance_counter(nums: Vec) -> i32 { 70 | let len = nums.len() as i32; 71 | // bit_one_counter[i]=k 表示nums数组中第i位为1的有k个,第i位为0的有len-k个 72 | // 这是种垂直方向一次性计数数组每个元素第i位的解法 73 | // 例如 数组长度位8, 有3个数字的第0位是1, 那么会有5个数字的第0位是0 74 | // 第0位对总的汉明距离的影响是5*3或3*5(因为两两成对计入,所以一个1和5个0凑对和1个0和3个1凑对是一样的) 75 | let mut bit_one_counter = [0_u16; 32]; 76 | for mut num in nums { 77 | let mut bit_index = 1; 78 | while num > 0 { 79 | // num & 0x01 取 num 最右的位 80 | if num & 0x01 == 1 { 81 | bit_one_counter[bit_index] += 1; 82 | } 83 | // num 右移1位,更新 num 的最后一位 84 | num >>= 1; 85 | bit_index += 1; 86 | } 87 | } 88 | let mut ret = 0; 89 | for count in bit_one_counter { 90 | let ones = i32::from(count); 91 | let zeros = len - ones; 92 | ret += ones * zeros; 93 | } 94 | ret 95 | } 96 | 97 | #[test] 98 | fn test_total_hamming_distance() { 99 | const TEST_CASES: [(&[i32], i32); 1] = [(&[4, 14, 2], 6)]; 100 | for (input, output) in TEST_CASES { 101 | assert_eq!(total_hamming_distance_brute_force(input.to_vec()), output); 102 | assert_eq!(total_hamming_distance_counter(input.to_vec()), output); 103 | } 104 | } 105 | 106 | /// https://leetcode.com/problems/sort-integers-by-the-number-of-1-bits/ 107 | fn sort_by_bits(mut arr: Vec) -> Vec { 108 | // arr.sort_by_cached_key(|&x| (x.count_ones, x)); 109 | arr.sort_unstable_by(|a, b| a.count_ones().cmp(&b.count_ones()).then(a.cmp(b))); 110 | arr 111 | } 112 | -------------------------------------------------------------------------------- /src/bitwise/is_power_of_x.rs: -------------------------------------------------------------------------------- 1 | /*! https://leetcode.com/problems/power-of-two/ 2 | https://leetcode.com/problems/power-of-three/ 3 | */ 4 | 5 | /// is_power_of_x一类题型的万能解法模板 6 | const fn is_power_of_two_normal_solution(mut n: i32) -> bool { 7 | if n == 0 { 8 | return false; 9 | } 10 | while n % 2 == 0 { 11 | n /= 2; 12 | } 13 | // 如果n能被2整除,那么跳出while循环必然是n=2/2=1,所以能被2整数时在这里是1 14 | n == 1 15 | } 16 | 17 | /** 如果一个数是2的幂,那么它的二进制表示中有且只有一位是1 18 | (n & -n): 只获取n的二进制的从左到右第一个1,其余位置0,如果n的二进制中只有一个1,那么(n & -n) == n 19 | Example1: 20 | 3 = 0011 21 | -3 = 1101 22 | 3 & -3 = 1 23 | Example2: 24 | 4 = 0100 25 | -4 = 1100 26 | 4 & -4 = 0100 27 | */ 28 | const fn is_power_of_two_bitwise_solution_1(n: i32) -> bool { 29 | if n == 0 { 30 | return false; 31 | } 32 | (n & -n) == n 33 | } 34 | 35 | /** 36 | (n - 1) 代表了将 n 最右边的 1 设置为 0,并且将较低位设置为 1 37 | (n & n-1): 去掉n的二进制中最后一个1,如果去掉之后等于0则说明是2的幂 38 | Example1: 39 | 3 = 0011 40 | 2 = 0010 41 | 3 & 2 = 1 42 | Example2: 43 | 4 = 0100 44 | 3 = 0011 45 | 4 & 3 = 0 46 | */ 47 | const fn is_power_of_two_bitwise_solution_2(n: i32) -> bool { 48 | if n == 0 { 49 | return false; 50 | } 51 | n & (n - 1) == 0 52 | } 53 | 54 | /** 55 | ## reproduce ve'rclippy::float_cmp 56 | error: strict comparison of `f32` or `f64` 57 | = note: `#[deny(clippy::float_cmp)]` on by default 58 | = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error_margin` 59 | const float EPSINON = 0.00001; 60 | if ((x >= - EPSINON) && (x <= EPSINON) {} 61 | */ 62 | #[cfg(not)] 63 | fn clippy_float() { 64 | assert_eq!(1f32, 1.0001f32); 65 | } 66 | 67 | #[test] 68 | #[allow(clippy::cast_precision_loss)] 69 | fn test_is_power_of_two() { 70 | /// https://stackoverflow.com/questions/5796983/checking-if-float-is-an-integer 71 | fn is_f32_an_integer(float: f32) -> bool { 72 | (float - float.round()).abs() < f32::EPSILON 73 | } 74 | use crate::code_snippets::random_i32::random_i32; 75 | for _ in 0..10_000 { 76 | let input = random_i32(); 77 | let input_is_power_of_2 = is_f32_an_integer((input as f32).log2()); 78 | assert_eq!( 79 | is_power_of_two_bitwise_solution_1(input), 80 | input_is_power_of_2 81 | ); 82 | } 83 | } 84 | 85 | /* 86 | ```text 87 | 1 0b1 88 | 3 0b11 89 | 9 0b1001 90 | 27 0b11011 91 | 81 0b1010001 92 | 243 0b11110011 93 | 729 0b1011011001 94 | ``` 95 | 3的幂的规律(自己瞎猜的) 96 | 1. 个位是1,3,9,7: n%3=0 or 1 97 | 2. 二进制最后一位是1: (n & -n) == 1 98 | 3. 二进制最高位是1: ? 99 | 100 | 获取从右边往左第一位非0 bit的索引位置的方法: math.log2(n&-n)+1 101 | 102 | # 官方解答: 103 | 104 | 1. 转换为3进制后做正则 105 | // Integer.toString(n, 3)表示将n转为3进制的字符串 106 | // 字符串是否以1 ^1 开头,后跟 0 或 多个 0 0* 并且不包含任何其他值 107 | return Integer.toString(n, 3).matches("^10*$"); 108 | 2. 使用库函数log 109 | if n <= 0: 110 | return False 111 | # >>> math.log(243, 3) 112 | # 4.999999999999999 113 | return math.log(n, 3).is_integer() 114 | 3. 借助i32中最大的3的幂(3^19) 115 | 因为 3 是质数,所以 3^19(i32范围内最大的3的幂)的除数只有 3^0, 3^1, ... 116 | 因此我们只需要将 3^19 除以 n 若余数为 0 意味着 n 是 3^19 的除数,因此是 3 的幂 117 | return n > 0 && 1162261467 % n == 0; 118 | */ 119 | //fn is_power_of_3() {} 120 | 121 | /** 122 | 4=0100的幂只可能在奇数位上有1 123 | 如果n是2的幂同时只有奇数位上是1(n & 0xaaaaaaaa == 0),那么n是4的幂 124 | */ 125 | #[allow(overflowing_literals)] 126 | const fn is_power_of_4_solution1(n: i32) -> bool { 127 | // the literal `0xaaaaaaaa` (decimal `2863311530`) does not fit into the type `i32` and will become `-1431655766i32` 128 | n > 0 && (n & -n) == n && n & 0xaaaa_aaaa == 0 129 | } 130 | 131 | /** 132 | 若 xx 为 2 的幂且 x%3 == 1,则 xx 为 4 的幂 133 | 证明方法: 4^k % 3 = (3+1)^k % 3 = ...(二项式展开) = 1 134 | */ 135 | const fn is_power_of_4_solution2(n: i32) -> bool { 136 | n > 0 && (n & -n) == n && n % 3 == 1 137 | } 138 | -------------------------------------------------------------------------------- /src/bitwise/mod.rs: -------------------------------------------------------------------------------- 1 | mod find_single_number; 2 | mod gray_code; 3 | mod hamming_distance_count_ones; 4 | mod is_power_of_x; 5 | mod nim_game; 6 | mod reverse_bits; 7 | -------------------------------------------------------------------------------- /src/bitwise/nim_game.rs: -------------------------------------------------------------------------------- 1 | /*! https://leetcode.com/problems/nim-game/ 2 | nim游戏规则:有若干个石头,两人每回合轮流拿走一些石头,每个人可以拿走1-3块石头 3 | 如果轮到A的回合时,石头数量是4的倍数,那么A必败(博弈问题的必败态) 4 | 或者利用二进制判断是不是 4 的倍数, 5 | 只需要通过和 3 (二进制 11)进行相与, 6 | 如果是 4 的倍数,那么结果一定是 0。 7 | 8 | 算法如下: 9 | x&3==0,则是4的倍数。 10 | 原理: 11 | 先来看一组数字的二进制表示 12 | 4    0100 13 | 8    1000 14 | 12 1100 15 | 16 10000 16 | 20 10100 17 | 由此可见 4 的倍数的二进制表示的后 2 为一定为 0。 18 | 19 | 从另外一个角度来看,4 的二进制表示是 0100,任何 4 的倍数一定是在此基础上增加 n 个 0100 20 | 由此也可得 4 的倍数的二进制表示的后 2 为一定为 0。 21 | */ 22 | const fn nim_game_bitwise(n: i32) -> bool { 23 | // (n % 4) != 0 24 | (n & 3) != 0 25 | } 26 | -------------------------------------------------------------------------------- /src/bitwise/reverse_bits.rs: -------------------------------------------------------------------------------- 1 | /// https://leetcode.com/problems/reverse-bits/ 2 | const fn reverse_bits(mut n: u32) -> u32 { 3 | let (mut ret, mut power) = (0_u32, 0_u32); 4 | while n != 0 { 5 | ret += (n & 1) << power; 6 | n >>= 1; 7 | power -= 1; 8 | } 9 | ret 10 | // n.reverse_bits() 11 | } 12 | -------------------------------------------------------------------------------- /src/code_snippets/lazy_static.rs: -------------------------------------------------------------------------------- 1 | /** 2 | # LazyStatic 3 | single_thread_immutable lazy_static stable rust implement, doesn't support no_std 4 | 5 | ## 适用于leetcode题型 6 | 7 | int_to_roman, roman_to_int(但是这两题用python更合适,需要HashMap有序) 8 | 9 | ## 另一种泛型约束写法`struct LazyStaticCell T> {`的缺点 10 | 11 | - 缺点1: 每个impl都要写老长 T> 12 | - 缺点2: 使用closure的语句都要多写闭包的函数签名,`static A: LazyStaticCell i32> = LazyStaticCell::new(|| 0_i32);`,可读性不好 13 | */ 14 | struct LazyStatic T> { 15 | init_once: std::sync::Once, 16 | /// 用Option为了让初次调用init_function初始化时能把F闭包给move掉 17 | init_function: std::cell::Cell>, 18 | /// 为了支持leetcode stable的rustc编译器,UnsafeCell内层不能用更好的MaybeUninit,因为MaybeUninit的write方法是unstable feature 19 | /// 所以这也是lazy_static源码中有个字段内是Option将来会换成MaybeUninit 20 | data: std::cell::UnsafeCell>, 21 | /// make our struct impl auto drop? 22 | _marker: std::marker::PhantomData, 23 | } 24 | 25 | /* 26 | warning: cross-crate traits with a default impl, like `Send`, should not be specialized 27 | --> src/code_snippets/lazy_static.rs:26:1 28 | | 29 | 26 | unsafe impl Send for LazyStatic {} 30 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 31 | | 32 | = note: `#[warn(suspicious_auto_trait_impls)]` on by default 33 | = warning: this will change its meaning in a future release! 34 | = note: for more information, see issue #93367 35 | note: try using the same sequence of generic parameters as the struct definition 36 | --> src/code_snippets/lazy_static.rs:14:1 37 | | 38 | 14 | / struct LazyStatic T> { 39 | */ 40 | #[allow(clippy::non_send_fields_in_send_ty)] 41 | unsafe impl Send for LazyStatic {} 42 | unsafe impl Sync for LazyStatic {} 43 | 44 | impl LazyStatic { 45 | const fn new(f: F) -> Self { 46 | Self { 47 | init_once: std::sync::Once::new(), 48 | init_function: std::cell::Cell::new(Some(f)), 49 | data: std::cell::UnsafeCell::new(None), 50 | _marker: std::marker::PhantomData, 51 | } 52 | } 53 | } 54 | 55 | impl T> LazyStatic { 56 | /// 为了解决错误`call expression requires function`,为了能让init_function字段能call,必须改变trait bound所以不能用原来泛型约束的self 57 | /// cast `F = fn() -> T` to `F: FnOnce() -> T` inorder to call self.init_function 58 | #[allow(clippy::use_self)] 59 | fn get(this: &LazyStatic) -> &T { 60 | this.init_once.call_once(|| { 61 | let data = this.init_function.take().unwrap()(); 62 | unsafe { 63 | *this.data.get() = Some(data); 64 | } 65 | }); 66 | unsafe { (*this.data.get()).as_ref().unwrap() } 67 | } 68 | } 69 | 70 | #[cfg(test)] 71 | mod test_lazy_static { 72 | use super::LazyStatic; 73 | use std::collections::HashMap; 74 | 75 | static GLOBAL_MAP: LazyStatic> = LazyStatic::new(|| { 76 | let mut map = HashMap::new(); 77 | map.insert(1, 1); 78 | map 79 | }); 80 | 81 | #[test] 82 | fn test_my_lazy_cell() { 83 | let map = LazyStatic::get(&GLOBAL_MAP); 84 | assert_eq!(map.get(&1), Some(&1)); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/code_snippets/mod.rs: -------------------------------------------------------------------------------- 1 | mod lazy_static; 2 | mod python_and_c_diff_when_calc_negative_number; 3 | pub mod random_i32; 4 | mod sorting; 5 | -------------------------------------------------------------------------------- /src/code_snippets/python_and_c_diff_when_calc_negative_number.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | # Rust RFC 2169-euclidean-modulo 3 | 4 | ## -1 / 10 5 | - Python/Ruby: -1 6 | - C/C++/Java/Rust: 0 7 | 8 | ## -1 % 10 9 | Python/Ruby: 9 10 | C/C++/Java/Rust: -1 11 | 12 | test: 13 | ruby -e "p -1 / 10" 14 | root -e "-1 % 10" 15 | */ 16 | 17 | /// https://github.com/rust-lang/rust/issues/75726 18 | /// https://stackoverflow.com/questions/31210357/is-there-a-modulus-not-remainder-function-operation 19 | /// rem_euclid can use in leetcode problem circular-array-loop 20 | #[test] 21 | #[allow(clippy::eq_op, clippy::identity_op)] 22 | fn test_rem_euclid() { 23 | assert_eq!(-1 / 10, 0); 24 | // `div_euclid` is same as python's `//` 25 | assert_eq!((-1_i32).div_euclid(10), -1); 26 | assert_eq!(-1 % 10, -1); 27 | // `rem_euclid` is same as python's `%` 28 | assert_eq!((-1_i32).rem_euclid(10), 9); 29 | } 30 | -------------------------------------------------------------------------------- /src/code_snippets/random_i32.rs: -------------------------------------------------------------------------------- 1 | /** 2 | 一个动态链接库(dynamic linking library, 也叫so文件)可以对应一个或多个头文件,例如boost库 3 | 例如 libc.so 就有 time.h, stdlib.h 等多个头文件 4 | TODO 为什么 openssl库 既有libcrypto.so也有libssl.so 5 | 可以通过`nm`命令工具获知某个so文件内有没有某个函数 6 | ```text 7 | [w@w-manjaro ~]$ nm -D /usr/lib/libc.so.6 | grep gmtime 8 | 00000000000bb480 T gmtime@@GLIBC_2.2.5 9 | 00000000000bb460 T __gmtime_r@@GLIBC_2.2.5 10 | 00000000000bb460 W gmtime_r@@GLIBC_2.2.5 11 | ``` 12 | 13 | 注意C语言的rand并不覆盖i32的范围,随即数的生成范围是[0, RAND_MAX] 14 | 而 RAND_MAX = i32::MAX 15 | */ 16 | pub fn random_i32() -> i32 { 17 | #[allow(non_camel_case_types)] 18 | type time_t = i64; // 根据libc源码中time_t的类型定义 19 | // `#[link(name = "c", kind = "dylib")]` is unnecessary, windows would compile error 20 | extern "C" { 21 | /// https://en.cppreference.com/w/cpp/chrono/c/time 22 | fn time(output: *mut time_t) -> time_t; 23 | fn rand() -> i32; 24 | fn srand(seed: u32); 25 | } 26 | 27 | use std::sync::Once; 28 | static INIT_RAND_SEED: Once = Once::new(); 29 | INIT_RAND_SEED.call_once(|| unsafe { 30 | srand(time(std::ptr::null_mut()) as u32); 31 | }); 32 | unsafe { rand() } 33 | } 34 | 35 | #[must_use] 36 | pub fn rand_range(min: i32, max: i32) -> i32 { 37 | const RAND_MAX: i32 = 0x7fff_ffff; 38 | let random_num = random_i32(); 39 | // 更精准点的随机数范围生成过程: min + random_num / (RAND_MAX / (max - min + 1) + 1) 40 | // rand() % 7的范围在[0,6],加上offset 1正好是[1,7] 41 | // 一般只记忆这个简单的 用MOD生成一定范围内的随机数 42 | random_num % max + min 43 | } 44 | -------------------------------------------------------------------------------- /src/compiler/evaluate_reverse_polish_notation.rs: -------------------------------------------------------------------------------- 1 | /// https://leetcode.com/problems/evaluate-reverse-polish-notation/ 2 | /// 类似题型: leetcode_682 3 | fn eval_rpn(tokens: Vec) -> i32 { 4 | let mut stack = vec![]; 5 | for token in tokens { 6 | let ch = token.as_bytes()[0]; 7 | //if ch.is_ascii_digit() || (ch == b'-' && token.len() > 1) { 8 | if ch.is_ascii_digit() || token.len() > 1 { 9 | stack.push(token.parse().unwrap()); 10 | } else { 11 | let rhs = stack.pop().unwrap(); 12 | let lhs = stack.last_mut().unwrap(); 13 | match ch { 14 | b'+' => *lhs += rhs, 15 | b'-' => *lhs -= rhs, 16 | b'*' => *lhs *= rhs, 17 | b'/' => *lhs /= rhs, 18 | _ => unreachable!(), 19 | } 20 | } 21 | } 22 | stack[0] 23 | } 24 | 25 | /* 26 | 解释测试用例(&["4","13","5","/","+"], 6) 27 | (4 + (13 / 5)) = 6 28 | stack = [4, 13, 5] 29 | ch = "/" 30 | rhs = stack.pop() 31 | lhs = stack.peek() 32 | lhs = lhs / rhs 33 | */ 34 | #[test] 35 | fn test_eval_rpn() { 36 | const TEST_CASES: [(&[&str], i32); 3] = [ 37 | (&["2", "1", "+", "3", "*"], 9), 38 | (&["4", "13", "5", "/", "+"], 6), 39 | ( 40 | &[ 41 | "10", "6", "9", "3", "+", "-11", "*", "/", "*", "17", "+", "5", "+", 42 | ], 43 | 22, 44 | ), 45 | ]; 46 | for (input, output) in TEST_CASES { 47 | let tokens = input.iter().map(|&x| x.to_string()).collect(); 48 | assert_eq!(eval_rpn(tokens), output); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/counter/anagrams.rs: -------------------------------------------------------------------------------- 1 | /// https://leetcode.com/problems/valid-anagram/ 2 | fn is_anagram(s: String, t: String) -> bool { 3 | if s.len() != t.len() { 4 | return false; 5 | } 6 | let mut counter = [0_u16; 26]; 7 | for each in s.into_bytes() { 8 | counter[(each - b'a') as usize] += 1; 9 | } 10 | for each in t.into_bytes() { 11 | let idx = (each - b'a') as usize; 12 | if counter[idx] == 0 { 13 | return false; 14 | } 15 | counter[idx] -= 1; 16 | } 17 | true 18 | } 19 | 20 | /// https://leetcode.com/problems/group-anagrams/ 21 | /// 由于Python没有原始数组,list是可变的不能Hash,所以list要转为tuple多了很多额外的操作 22 | fn group_anagrams(strs: Vec) -> Vec> { 23 | let mut group = std::collections::HashMap::new(); 24 | for s in strs { 25 | let mut counter = [0_u8; 26]; 26 | for &byte in s.as_bytes() { 27 | counter[(byte - b'a') as usize] += 1; 28 | } 29 | group.entry(counter).or_insert_with(Vec::new).push(s); 30 | } 31 | group.into_values().collect() 32 | } 33 | 34 | /// https://leetcode.com/problems/find-anagram-mappings/ 35 | /// 有两个互为anagram的数组(anagram的定义请看valid_anagram一题) 36 | /// 请你找出A的每个元素在B中的索引,如有重复元素则返回任意一个索引 37 | /// 例如A=[12,28],B=[28,12],因为A[0]=B[1],A[1]=B[0],所以返回[1,0] 38 | fn anagram_mappings(mut a: Vec, b: Vec) -> Vec { 39 | let mut map = std::collections::HashMap::with_capacity(b.len()); 40 | for (i, b) in b.into_iter().enumerate() { 41 | map.insert(b, i as i32); 42 | } 43 | for a in &mut a { 44 | *a = map[a]; 45 | } 46 | a 47 | } 48 | -------------------------------------------------------------------------------- /src/counter/count_num1_square_eq_two_num2_product.rs: -------------------------------------------------------------------------------- 1 | /*! https://leetcode.com/problems/number-of-ways-where-square-of-number-is-equal-to-product-of-two-numbers/ 2 | 周赛#205的第二题 3 | 解释测试用例1: 4*4=2*8, 所以得到三元组(1,1,2) 4 | 三元组的要求 5 | 1. nums1的某个数的平方=nums2某两个数的乘积 6 | 2. nums2的某个数的平方=nums1某两个数的乘积 7 | 8 | 解题思路1: 排序后能不能批量数方案?two_sum? 9 | 解题思路2: 因数分解num*num,看看另一个数组是否存在这些因子 10 | */ 11 | fn num_triplets(nums1: Vec, nums2: Vec) -> i32 { 12 | #[allow(clippy::option_if_let_else)] 13 | fn helper(nums1: &[i32], nums2: &[i32]) -> i32 { 14 | // 用HashMap加快在num2中找因子的过程, key: num2, value: count of num2 in nums2 15 | let mut counter = std::collections::HashMap::with_capacity(nums2.len()); 16 | for &num in nums2 { 17 | if let Some(cnt) = counter.get_mut(&num) { 18 | *cnt += 1; 19 | } else { 20 | counter.insert(num, 1); 21 | } 22 | } 23 | 24 | let mut res = 0; 25 | for &num1 in nums1 { 26 | let target = num1 * num1; 27 | for factor1 in 1..num1 { 28 | if target % factor1 != 0 { 29 | continue; 30 | } 31 | let factor2 = target / factor1; 32 | if let (Some(cnt1), Some(cnt2)) = (counter.get(&factor1), counter.get(&factor2)) { 33 | res += cnt1 * cnt2; 34 | } 35 | } 36 | // factor1 == factor2的情况,应对类似([1,1], [1,1,1])这样有重复的输入用例 37 | if let Some(cnt) = counter.get(&num1) { 38 | res += cnt * (cnt - 1) / 2; 39 | } 40 | } 41 | res 42 | } 43 | 44 | helper(&nums1, &nums2) + helper(&nums2, &nums1) 45 | } 46 | 47 | #[test] 48 | fn test_diagonal_sum() { 49 | const TEST_CASES: [(&[i32], &[i32], i32); 4] = [ 50 | (&[1, 1], &[1, 1, 1], 9), 51 | (&[7, 4], &[5, 2, 8, 9], 1), 52 | (&[7, 7, 8, 3], &[1, 2, 9, 7], 2), 53 | (&[4, 7, 9, 11, 23], &[3, 5, 1024, 12, 18], 0), 54 | ]; 55 | for (nums1, nums2, res) in TEST_CASES { 56 | assert_eq!(num_triplets(nums1.to_vec(), nums2.to_vec()), res); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/counter/find_all_numbers_disappeared_in_an_array.rs: -------------------------------------------------------------------------------- 1 | /// https://leetcode.com/problems/find-all-numbers-disappeared-in-an-array/ 2 | /// nums[i] is in the range [1, n], return an array of all the integers in the range [1, n] that do not appear in nums 3 | /// 所谓In-Place的解法过于投机取巧,难以理解,不想照抄官方解答 4 | fn find_disappeared_numbers(nums: Vec) -> Vec { 5 | let n = nums.len(); 6 | let mut counter = vec![false; n + 1]; 7 | for num in nums { 8 | counter[num as usize] = true; 9 | } 10 | let mut ret = vec![]; 11 | for (num, is_appeared) in counter.into_iter().skip(1).enumerate() { 12 | if !is_appeared { 13 | ret.push(num as i32 + 1); 14 | } 15 | } 16 | ret 17 | } 18 | -------------------------------------------------------------------------------- /src/counter/increasing_decreasing_string.rs: -------------------------------------------------------------------------------- 1 | /// https://leetcode.com/problems/increasing-decreasing-string/ 2 | #[allow(clippy::needless_range_loop)] 3 | fn sort_string(s: String) -> String { 4 | let n = s.len(); 5 | let mut counter = [0_u8; 26]; 6 | for byte in s.into_bytes() { 7 | counter[(byte - b'a') as usize] += 1; 8 | } 9 | 10 | let mut ret = Vec::with_capacity(n); 11 | while ret.len() < n { 12 | for i in 0..26 { 13 | if counter[i] > 0 { 14 | counter[i] -= 1; 15 | ret.push(b'a' + i as u8); 16 | } 17 | } 18 | for i in (0..26).rev() { 19 | if counter[i] > 0 { 20 | counter[i] -= 1; 21 | ret.push(b'a' + i as u8); 22 | } 23 | } 24 | } 25 | 26 | unsafe { String::from_utf8_unchecked(ret) } 27 | } 28 | 29 | #[test] 30 | fn test_sort_string() { 31 | const TEST_CASES: [(&str, &str); 2] = 32 | [("aaaabbbbcccc", "abccbaabccba"), ("leetcode", "cdelotee")]; 33 | for (input, output) in TEST_CASES { 34 | assert_eq!(sort_string(input.to_string()), output.to_string()); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/counter/number_of_equivalent_domino_pairs.rs: -------------------------------------------------------------------------------- 1 | /// https://leetcode.com/problems/number-of-equivalent-domino-pairs/ 2 | /// 既然nums[i][0]和nums[i][1]的取值范围都在0-9,那么可以聚合成一对2位十进制数,为了方便比较我们规定让较小的数放在十位 3 | fn num_equiv_domino_pairs(dominoes: Vec>) -> i32 { 4 | let mut counter = [0_u16; 100]; 5 | let mut ret = 0; 6 | for each in dominoes { 7 | let index = if each[1] < each[0] { 8 | each[1] * 10 + each[0] 9 | } else { 10 | each[0] * 10 + each[1] 11 | } as usize; 12 | // 这里是C(n,2)的计数方式 13 | ret += i32::from(counter[index]); 14 | counter[index] += 1; 15 | } 16 | ret 17 | } 18 | 19 | #[test] 20 | fn test_num_equiv_domino_pairs() { 21 | let test_cases = vec![(vec_vec![[1, 2], [2, 1], [3, 4], [5, 6]], 1)]; 22 | for (input, output) in test_cases { 23 | assert_eq!(num_equiv_domino_pairs(input), output); 24 | } 25 | } 26 | 27 | /// 超时解答 28 | #[cfg(not)] 29 | fn num_equiv_domino_pairs_brute_force(dominoes: Vec>) -> i32 { 30 | let n = dominoes.len(); 31 | let mut ret = 0; 32 | for i in 0..n { 33 | let (a, b) = (dominoes[i][0], dominoes[i][1]); 34 | let sum_a_b = a + b; 35 | for j in i + 1..n { 36 | let (c, d) = (dominoes[j][0], dominoes[j][1]); 37 | if c + d == sum_a_b && (a == c || a == d) { 38 | ret += 1; 39 | } 40 | } 41 | } 42 | ret 43 | } 44 | -------------------------------------------------------------------------------- /src/counter/number_of_good_pairs.rs: -------------------------------------------------------------------------------- 1 | /** https://leetcode.com/problems/number-of-good-pairs/ 2 | count if nums[i] == nums[j] and i < j 3 | 思路: collection.Counter统计输入数组每个数字的出现次数,例如3出现了3次,那么就有math.comb(3,2)=3*2=6对满足i return sum(map(lambda v: math.comb(v, 2), collections.Counter(nums).values())) 7 | */ 8 | fn num_identical_pairs(nums: Vec) -> i32 { 9 | let mut counter = [0_u8; 101]; 10 | for num in nums { 11 | counter[num as usize] += 1; 12 | } 13 | // array暂不支持into_iter, issue#66145 14 | counter 15 | .iter() 16 | .map(|&v| (i32::from(v) - 1) * i32::from(v) / 2) 17 | .sum::() 18 | } 19 | 20 | /// 以下都是number_of_good_pairs不断迭代的过程(counter算法的几重境界) 21 | fn number_of_good_pairs_v1(nums: Vec) -> i32 { 22 | let mut counter = std::collections::HashMap::::new(); 23 | for num in nums { 24 | *counter.entry(num).or_default() += 1; 25 | } 26 | counter.iter().map(|(_k, &v)| (v - 1) * v / 2).sum() 27 | } 28 | 29 | fn number_of_good_pairs_v2(nums: Vec) -> i32 { 30 | let mut counter = std::collections::HashMap::::with_capacity(nums.len()); 31 | for num in nums { 32 | *counter.entry(num).or_default() += 1; 33 | } 34 | counter.into_values().map(|v| (v - 1) * v / 2).sum() 35 | } 36 | 37 | /** 38 | 既然题目提示了nums的长度范围是1..=100,意味着计数的范围也是1到100 39 | 那么or_insert(0)可以优化成or_insert(0_u8),0_u8比默认的0_i32节省3/4内存 40 | 但是HashMap的value是u8的话,(v-1)*v/2会有两个溢出问题: 41 | 1. v-1当v=0时会向下溢出成负数,v-1要改成v.saturating_sub(1) 42 | 2. (V-1)*v两个u8相乘可能会超过255 43 | */ 44 | fn number_of_good_pairs_v3(nums: Vec) -> i32 { 45 | let mut counter = std::collections::HashMap::::with_capacity(nums.len()); 46 | for num in nums { 47 | *counter.entry(num).or_default() += 1; 48 | } 49 | counter 50 | .into_values() 51 | .map(|v| (i32::from(v) - 1) * i32::from(v) / 2) 52 | .sum::() 53 | } 54 | -------------------------------------------------------------------------------- /src/data_structure/heap/kth_largest_element_in_a_stream.rs: -------------------------------------------------------------------------------- 1 | /*! https://leetcode.com/problems/kth-largest-element-in-a-stream/ 2 | 类似于福布斯富豪榜,新富豪和富豪榜中末位比,如果新富豪更有钱就能挤上去 3 | 代码实现上维护一个大小为k的小根堆 4 | 如果富豪人数小于k则持续入堆 5 | 如果富豪人数等于k则 6 | 如果新富豪比末位富豪更有钱,则踢掉最穷的富豪,新富豪入堆,重新heapify 7 | 否则小根堆维持不变 8 | 9 | 为什么用小根堆? 10 | 「末尾淘汰制度」的思想,堆里面至多有k个元素,新来的元素至少要击败其中一个,才能进入堆 11 | 说是小根堆,实际上存的是最大的几个,类似福布斯富豪榜,新来的必须足够大才能上榜 12 | */ 13 | use std::cmp::Reverse; 14 | use std::collections::BinaryHeap; 15 | 16 | struct KthLargest { 17 | min_heap: BinaryHeap>, 18 | k: usize, 19 | } 20 | 21 | impl KthLargest { 22 | /// nums.len() > k 23 | fn new(k: i32, nums: Vec) -> Self { 24 | let k = k as usize; 25 | let mut min_heap = BinaryHeap::with_capacity(k); 26 | for num in nums { 27 | if min_heap.len() < k { 28 | min_heap.push(Reverse(num)); 29 | continue; 30 | } 31 | if num > min_heap.peek().unwrap().0 { 32 | // heapq.heapreplace 33 | min_heap.pop().unwrap(); 34 | min_heap.push(Reverse(num)); 35 | } 36 | } 37 | Self { min_heap, k } 38 | } 39 | 40 | fn add(&mut self, val: i32) -> i32 { 41 | if self.min_heap.len() < self.k { 42 | self.min_heap.push(Reverse(val)); 43 | } else if val > self.min_heap.peek().unwrap().0 { 44 | self.min_heap.pop().unwrap(); 45 | self.min_heap.push(Reverse(val)); 46 | } 47 | self.min_heap.peek().unwrap().0 48 | } 49 | } 50 | 51 | #[test] 52 | fn test_kth_in_a_stream() { 53 | let mut kth = KthLargest::new(3, vec![4, 5, 8, 2]); 54 | assert_eq!(kth.add(3), 4); 55 | assert_eq!(kth.add(5), 5); 56 | assert_eq!(kth.add(10), 5); 57 | assert_eq!(kth.add(9), 8); 58 | assert_eq!(kth.add(4), 8); 59 | } 60 | -------------------------------------------------------------------------------- /src/data_structure/heap/mod.rs: -------------------------------------------------------------------------------- 1 | mod kth_largest_element_in_a_stream; 2 | #[allow(clippy::module_name_repetitions)] 3 | pub mod my_max_heap; 4 | mod seat_reservation_manager; 5 | mod single_threaded_cpu; 6 | mod sliding_window_median; 7 | mod top_k_frequent_elements; 8 | -------------------------------------------------------------------------------- /src/data_structure/heap/seat_reservation_manager.rs: -------------------------------------------------------------------------------- 1 | //! https://leetcode.com/problems/seat-reservation-manager/ 2 | 3 | struct SeatManagerBruteForce { 4 | // seat[i]=true,表示座位i可以被预约 5 | seat: Vec, 6 | len: usize, 7 | } 8 | 9 | impl SeatManagerBruteForce { 10 | fn new(n: i32) -> Self { 11 | let len = n as usize + 1; 12 | Self { 13 | seat: vec![true; len], 14 | len, 15 | } 16 | } 17 | 18 | /// 优化: 动态数据中频繁取最值可以用`BinaryHeap`,取最小值耗时O(1),插入耗时O(logn) 19 | /// 当然这题用BTreeSet也能AC,只不过first_entry还在nightly不方便操作 20 | fn reserve(&mut self) -> i32 { 21 | for i in 1..self.len { 22 | if self.seat[i] { 23 | self.seat[i] = false; 24 | return i as i32; 25 | } 26 | } 27 | unreachable!() 28 | } 29 | 30 | fn unreserve(&mut self, seat_number: i32) { 31 | self.seat[seat_number as usize] = true; 32 | } 33 | } 34 | 35 | struct SeatManager(std::collections::BinaryHeap); 36 | 37 | impl SeatManager { 38 | fn new(n: i32) -> Self { 39 | let mut heap = std::collections::BinaryHeap::new(); 40 | for i in 1..=n { 41 | heap.push(-i); 42 | } 43 | Self(heap) 44 | } 45 | 46 | fn reserve(&mut self) -> i32 { 47 | -self.0.pop().unwrap() 48 | } 49 | 50 | fn unreserve(&mut self, seat_number: i32) { 51 | self.0.push(-seat_number); 52 | } 53 | } 54 | 55 | #[test] 56 | fn test_seat_reservation_manager() { 57 | let mut seat = SeatManager::new(5); 58 | assert_eq!(seat.reserve(), 1); // 所有座位都可以预约,所以返回最小编号的座位,也就是 1 。 59 | assert_eq!(seat.reserve(), 2); // 可以预约的座位为 [2,3,4,5] ,返回最小编号的座位,也就是 2 。 60 | seat.unreserve(2); // 将座位 2 变为可以预约,现在可预约的座位为 [2,3,4,5] 。 61 | assert_eq!(seat.reserve(), 2); // 可以预约的座位为 [2,3,4,5] ,返回最小编号的座位,也就是 2 。 62 | assert_eq!(seat.reserve(), 3); // 可以预约的座位为 [3,4,5] ,返回最小编号的座位,也就是 3 。 63 | assert_eq!(seat.reserve(), 4); // 可以预约的座位为 [4,5] ,返回最小编号的座位,也就是 4 。 64 | assert_eq!(seat.reserve(), 5); // 唯一可以预约的是座位 5 ,所以返回 5 。 65 | seat.unreserve(5); // 将座位 5 变为可以预约,现在可预约的座位为 [5] 。 66 | } 67 | -------------------------------------------------------------------------------- /src/data_structure/heap/single_threaded_cpu.rs: -------------------------------------------------------------------------------- 1 | #[derive(PartialEq, Eq, Debug)] 2 | struct Task { 3 | created_at: i32, 4 | processing_time: i32, 5 | index: i32, 6 | } 7 | 8 | impl PartialOrd for Task { 9 | fn partial_cmp(&self, other: &Self) -> Option { 10 | Some( 11 | self.processing_time 12 | .cmp(&other.processing_time) 13 | .then(self.index.cmp(&other.index)) 14 | .reverse(), 15 | ) 16 | } 17 | } 18 | 19 | impl Ord for Task { 20 | fn cmp(&self, other: &Self) -> std::cmp::Ordering { 21 | self.partial_cmp(other).unwrap() 22 | } 23 | } 24 | 25 | /// https://leetcode.com/problems/single-threaded-cpu/ 26 | fn single_threaded_cpu(tasks: Vec>) -> Vec { 27 | let len = tasks.len(); 28 | let mut tasks = tasks 29 | .into_iter() 30 | .enumerate() 31 | .map(|(index, task)| Task { 32 | created_at: task[0], 33 | processing_time: task[1], 34 | index: index as i32, 35 | }) 36 | .collect::>(); 37 | tasks.sort_unstable_by_key(|task| std::cmp::Reverse(task.created_at)); 38 | let mut task_queue = std::collections::BinaryHeap::new(); 39 | let mut timestamp = 0; 40 | let mut ret = Vec::with_capacity(len); 41 | for _ in 0..len { 42 | if task_queue.is_empty() { 43 | timestamp = timestamp.max(tasks.last().unwrap().created_at); 44 | } 45 | while !tasks.is_empty() && tasks.last().unwrap().created_at <= timestamp { 46 | task_queue.push(tasks.pop().unwrap()); 47 | } 48 | let done_task = task_queue.pop().unwrap(); 49 | timestamp += done_task.processing_time; 50 | ret.push(done_task.index); 51 | } 52 | ret 53 | } 54 | 55 | #[test] 56 | fn test_single_threaded_cpu() { 57 | let test_cases = vec![ 58 | (vec_vec![[1, 2], [2, 4], [3, 2], [4, 1]], vec![0, 2, 3, 1]), 59 | ( 60 | vec_vec![[7, 10], [7, 12], [7, 5], [7, 4], [7, 2]], 61 | vec![4, 3, 2, 0, 1], 62 | ), 63 | ]; 64 | for (input, output) in test_cases { 65 | assert_eq!(single_threaded_cpu(input), output); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/data_structure/heap/top_k_frequent_elements.rs: -------------------------------------------------------------------------------- 1 | //! https://leetcode.com/problems/top-k-frequent-elements/ 2 | //! 解题思路类似: 3 | 4 | /// return [num for num, _ in collections.Counter(nums).most_common(k)] 5 | fn top_k_frequent_elements(nums: Vec, k: i32) -> Vec { 6 | let k = k as usize; 7 | let n = nums.len(); 8 | let mut counter = std::collections::HashMap::::with_capacity(n); 9 | for &num in &nums { 10 | *counter.entry(num).or_default() += 1; 11 | } 12 | // 小根堆: (-出现次数, 数字),所以堆顶会是出现次数最低的数字,随时可以被别人挤掉 13 | let mut heap = std::collections::BinaryHeap::<(i32, i32)>::with_capacity(k); 14 | for (&num, &cnt) in &counter { 15 | if heap.len() == k { 16 | if -cnt < heap.peek().unwrap().0 { 17 | heap.pop(); 18 | heap.push((-cnt, num)); 19 | } 20 | } else { 21 | heap.push((-cnt, num)); 22 | } 23 | } 24 | heap.into_iter().rev().map(|(_, num)| num).collect() 25 | } 26 | 27 | /// https://leetcode.com/problems/top-k-frequent-words/ 28 | fn top_k_frequent_words_sorting_solution(words: Vec, k: i32) -> Vec { 29 | let mut counter = std::collections::HashMap::::new(); 30 | for word in words { 31 | *counter.entry(word).or_default() += 1; 32 | } 33 | let mut a = counter.into_iter().collect::>(); 34 | a.sort_unstable_by(|(word_a, freq_a), (word_b, freq_b)| { 35 | freq_b.cmp(freq_a).then(word_a.cmp(word_b)) 36 | }); 37 | a.into_iter() 38 | .take(k as usize) 39 | .map(|(word, _freq)| word) 40 | .collect() 41 | } 42 | 43 | /// [wrong answer!]fast than sort, sort has extra overhead on array elements swap O(n), but heap swap overhead is O(k) 44 | fn top_k_frequent_words_heap_solution(words: Vec, k: i32) -> Vec { 45 | let mut counter = std::collections::BTreeMap::::new(); 46 | for word in words { 47 | *counter.entry(word).or_default() += 1; 48 | } 49 | let k = k as usize; 50 | let mut min_heap = std::collections::BinaryHeap::with_capacity(k + 1); 51 | for (word, freq) in counter { 52 | // python3 heap pushpop 53 | // word is sort by letter desc 54 | min_heap.push((-freq, word)); 55 | if min_heap.len() > k { 56 | min_heap.pop(); 57 | } 58 | } 59 | // min_heap is sort by freq asc and letter desc, need to reverse it 60 | min_heap 61 | .into_iter() 62 | .rev() 63 | .map(|(_freq, word)| word) 64 | .collect() 65 | } 66 | 67 | #[test] 68 | fn test_top_k_frequent_words() { 69 | let test_cases = vec![ 70 | ( 71 | vec_string!["i", "love", "leetcode", "i", "love", "coding"], 72 | 2, 73 | vec_string!["i", "love"], 74 | ), 75 | ( 76 | vec_string!["the", "day", "is", "sunny", "the", "the", "the", "sunny", "is", "is"], 77 | 4, 78 | vec_string!["the", "is", "sunny", "day"], 79 | ), 80 | ]; 81 | for (words, k, top_k_frequent) in test_cases { 82 | // since HashMap into_iter is unordered, my heap solution sometimes pass test_cases sometimes not pass, if I change to BTreeMap it would 100% not pass 83 | //assert_eq!(top_k_frequent_words_heap_solution(words.clone(), k), top_k_frequent); 84 | assert_eq!( 85 | top_k_frequent_words_sorting_solution(words, k), 86 | top_k_frequent 87 | ); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/data_structure/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod heap; 2 | mod monotonic_queue_sliding_window_max; 3 | mod monotonic_stack_next_greater_element_2; 4 | mod trie_edit_distance; 5 | mod union_find; 6 | -------------------------------------------------------------------------------- /src/data_structure/monotonic_queue_sliding_window_max.rs: -------------------------------------------------------------------------------- 1 | use std::collections::VecDeque; 2 | 3 | struct MonotonicQueue(VecDeque); 4 | 5 | impl MonotonicQueue { 6 | fn new(cap: usize) -> Self { 7 | Self(VecDeque::with_capacity(cap)) 8 | } 9 | 10 | fn push(&mut self, num: i32) { 11 | while let Some(&back) = self.0.back() { 12 | // 类似单调栈,新来num的会从左到右踢掉老的较小的数 13 | if back < num { 14 | self.0.pop_back().unwrap(); 15 | } else { 16 | break; 17 | } 18 | } 19 | self.0.push_back(num); 20 | } 21 | 22 | fn pop(&mut self, num: i32) { 23 | if let Some(&front) = self.0.front() { 24 | // 之所以要看一下最老的一个元素值是否等于滑动窗口出队的值,是为了避免push的是否该值已被「踢掉」,如果push的时候被踢掉了,pop的时候就啥也不用管 25 | if front == num { 26 | self.0.pop_front().unwrap(); 27 | } 28 | } 29 | } 30 | } 31 | 32 | fn max_sliding_window(nums: Vec, k: i32) -> Vec { 33 | // 超时: nums.windows(k as usize).map(|window| *window.iter().max().unwrap()).collect() 34 | let n = nums.len(); 35 | let k = k as usize; 36 | let mut monotonic_queue = MonotonicQueue::new(n); 37 | let mut i = 0_usize; 38 | let mut res = Vec::with_capacity(n - k + 1); 39 | while i < k - 1 { 40 | monotonic_queue.push(nums[i]); 41 | i += 1; 42 | } 43 | while i < n { 44 | monotonic_queue.push(nums[i]); 45 | res.push(*monotonic_queue.0.front().unwrap()); 46 | monotonic_queue.pop(nums[i + 1 - k]); 47 | i += 1; 48 | } 49 | res 50 | } 51 | 52 | #[test] 53 | fn test_max_sliding_window() { 54 | const TEST_CASES: [(&[i32], i32, &[i32]); 1] = 55 | [(&[1, 3, -1, -3, 5, 3, 6, 7], 3, &[3, 3, 5, 5, 6, 7])]; 56 | for (nums, k, expected) in TEST_CASES { 57 | assert_eq!(max_sliding_window(nums.to_vec(), k), expected.to_vec()); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/data_structure/monotonic_stack_next_greater_element_2.rs: -------------------------------------------------------------------------------- 1 | /*! https://leetcode.com/problems/next-greater-element-ii/ 2 | https://mp.weixin.qq.com/s?__biz=MzAxODQxMDM0Mw==&mid=2247484525&idx=1&sn=3d2e63694607fec72455a52d9b15d4e5&chksm=9bd7fa65aca073734df90b45054448e09c14e6e35ad7b778bff62f9bd6c2b4f6e1ca7bc4f844&scene=21#wechat_redirect 3 | 由于入参是一个循环数组,按照旋转排序数组[rotate string一题查找某个字符串能否通过s循环移位后得到]的经验 4 | 是原数组翻倍,也就是后面再接上一个原数组 5 | */ 6 | fn next_greater_elements(nums: Vec) -> Vec { 7 | let n = nums.len(); 8 | if n == 0 { 9 | return Vec::with_capacity(0); 10 | } 11 | let mut ret = vec![-1; n]; 12 | let mut stack: Vec = Vec::with_capacity(n); 13 | for i in (0..(2 * n - 1)).rev() { 14 | while let Some(&last) = stack.last() { 15 | if last > nums[i % n] { 16 | break; 17 | } 18 | stack.pop().unwrap(); 19 | } 20 | // while !stack.is_empty() && stack.last().unwrap().le(&nums[i % n]) { 21 | // stack.pop(); 22 | // } 23 | if let Some(peek) = stack.last() { 24 | ret[i % n] = *peek; 25 | } 26 | stack.push(nums[i % n]); 27 | } 28 | ret 29 | } 30 | 31 | #[test] 32 | fn test_next_greater_elements() { 33 | const TEST_CASES: [(&[i32], &[i32]); 1] = [(&[1, 2, 1], &[2, -1, 2])]; 34 | for (nums, output) in TEST_CASES { 35 | assert_eq!(next_greater_elements(nums.to_vec()), output.to_vec()); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/data_structure/union_find/friend_circles.rs: -------------------------------------------------------------------------------- 1 | /// https://leetcode.com/problems/number-of-provinces/ 2 | /// https://leetcode.com/problems/friend-circles/ 3 | /// 以前这题叫「朋友圈」,不知道为什么改名为「省份数量」,其实就是数邻接矩阵中相连岛屿的数量 4 | use super::UnionFind; 5 | 6 | /// 入参is_connected是一个图的邻接矩阵 7 | fn union_find(is_connected: Vec>) -> i32 { 8 | let n = is_connected.len(); 9 | let mut uf = UnionFind::new(n); 10 | for (i, row) in is_connected.into_iter().enumerate() { 11 | for (j, each) in row.into_iter().enumerate() { 12 | if each == 1 { 13 | uf.union(i, j); 14 | } 15 | } 16 | } 17 | 18 | // 数一下并查集中有几棵树 19 | let mut ret = 0; 20 | for (i, parent) in uf.parents.into_iter().enumerate() { 21 | // 并查集中parent==i表示节点i自己就是根节点 22 | if parent == i { 23 | ret += 1; 24 | } 25 | } 26 | ret 27 | } 28 | 29 | fn bfs(is_connected: Vec>) -> i32 { 30 | let n = is_connected.len(); 31 | let mut visited = vec![false; n]; 32 | let mut queue = std::collections::VecDeque::with_capacity(n); 33 | let mut ret = 0; 34 | for start in 0..n { 35 | if visited[start] { 36 | continue; 37 | } 38 | queue.push_back(start); 39 | while let Some(cur) = queue.pop_front() { 40 | visited[cur] = true; 41 | for (next, &connected) in is_connected[cur].iter().enumerate().take(n) { 42 | if connected == 0 || visited[next] { 43 | continue; 44 | } 45 | queue.push_back(next); 46 | } 47 | } 48 | ret += 1; 49 | } 50 | ret 51 | } 52 | 53 | #[test] 54 | fn test_friends_circle() { 55 | let test_cases = vec![ 56 | (vec_vec![[1, 1, 0], [1, 1, 0], [0, 0, 1]], 2), 57 | (vec_vec![[1, 1, 0], [1, 1, 1], [0, 1, 1]], 1), 58 | ]; 59 | for (input, output) in test_cases { 60 | assert_eq!(union_find(input.clone()), output); 61 | assert_eq!(bfs(input.clone()), output); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/data_structure/union_find/min_cost_to_connect_all_points.rs: -------------------------------------------------------------------------------- 1 | use super::UnionFind; 2 | 3 | /// https://leetcode.com/problems/min-cost-to-connect-all-points/ 4 | /// 平面上有若干点,找出能连接所有点的最短边的总和 5 | /// 除了并查集排除重复连边,还能用「最小生成树的模板」的Prim或Kruskal算法 6 | fn min_cost_connect_points(points: Vec>) -> i32 { 7 | let n = points.len(); 8 | let mut edges = Vec::with_capacity(n * (n - 1) / 2); 9 | for start in 0..n { 10 | for end in start + 1..n { 11 | edges.push(( 12 | (points[start][0] - points[end][0]).abs() 13 | + (points[start][1] - points[end][1]).abs(), 14 | start, 15 | end, 16 | )); 17 | } 18 | } 19 | edges.sort_unstable(); 20 | let mut total_cost = 0; 21 | let mut union_find = UnionFind::new(n); 22 | let mut used_edges = 0; 23 | for (cost, node_a, node_b) in edges { 24 | let root_a = union_find.find_root(node_a); 25 | let root_b = union_find.find_root(node_b); 26 | if root_a == root_b { 27 | continue; 28 | } 29 | // 如果a和b不相连,则添加一条node_a连向node_b边 30 | union_find.parents[root_b] = root_a; 31 | total_cost += cost; 32 | used_edges += 1; 33 | if used_edges == n - 1 { 34 | break; 35 | } 36 | } 37 | total_cost 38 | } 39 | 40 | #[test] 41 | fn test_min_cost_connect_points() { 42 | let test_cases = vec![ 43 | (vec_vec![[0, 0], [2, 2], [3, 10], [5, 2], [7, 0]], 20), 44 | (vec_vec![[3, 12], [-2, 5], [-4, 1]], 18), 45 | (vec_vec![[0, 0], [1, 1], [1, 0], [-1, 1]], 4), 46 | (vec_vec![[2, -3], [-17, -8], [13, 8], [-17, -15]], 53), 47 | ]; 48 | for (points, min_cost) in test_cases { 49 | assert_eq!(min_cost_connect_points(points), min_cost); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/data_structure/union_find/mod.rs: -------------------------------------------------------------------------------- 1 | mod friend_circles; 2 | mod min_cost_to_connect_all_points; 3 | 4 | struct UnionFind { 5 | parents: Vec, 6 | } 7 | 8 | impl UnionFind { 9 | #[inline] 10 | fn new(n: usize) -> Self { 11 | Self { 12 | parents: (0..n).collect(), 13 | } 14 | } 15 | 16 | fn find_root(&self, node: usize) -> usize { 17 | let mut curr_node = node; 18 | let mut curr_node_parent = self.parents[curr_node]; 19 | while curr_node_parent != curr_node { 20 | curr_node = curr_node_parent; 21 | curr_node_parent = self.parents[curr_node]; 22 | } 23 | curr_node_parent 24 | } 25 | 26 | fn union(&mut self, node_a: usize, node_b: usize) { 27 | // 路径压缩: 不要直接将b连到a上,而是将b的祖先连向a的祖先,以此压缩路径减少连边 28 | let root_a = self.find_root(node_a); 29 | let root_b = self.find_root(node_b); 30 | if root_a != root_b { 31 | // 将b的祖先挂载到a的祖先下 32 | self.parents[root_b] = root_a; 33 | } 34 | } 35 | } 36 | 37 | struct UnionFindConstGenerics { 38 | parents: [usize; N], 39 | } 40 | 41 | impl UnionFindConstGenerics { 42 | const fn new() -> Self { 43 | let mut parents = [0; N]; 44 | let mut i = 0; 45 | while i < N { 46 | parents[i] = i; 47 | i += 1; 48 | } 49 | Self { parents } 50 | } 51 | 52 | const fn find_root(&self, node: usize) -> usize { 53 | let mut cur = node; 54 | let mut cur_parent = self.parents[cur]; 55 | while cur_parent != cur { 56 | cur = cur_parent; 57 | cur_parent = self.parents[cur]; 58 | } 59 | cur_parent 60 | } 61 | 62 | fn union(&mut self, node_a: usize, node_b: usize) { 63 | let root_a = self.find_root(node_a); 64 | let root_b = self.find_root(node_b); 65 | if root_a != root_b { 66 | // 将b的祖先挂载到a的祖先下 67 | self.parents[root_b] = root_a; 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/dp/burst_balloons.rs: -------------------------------------------------------------------------------- 1 | /// https://leetcode.com/problems/burst-balloons/ 2 | fn burst_balloons(mut nums: Vec) -> i32 { 3 | nums.insert(0, 1); 4 | nums.push(1); 5 | let n = nums.len(); 6 | // 初始值和从区间长度3开始遍历恰好能让区间长度2以内的得分全为0,使得dp计算区间长度为3的得分时为 0+戳中间气球得分+0 7 | // dp[i][j]表示区间[i,j]的最大得分,只有当区间长度大于3时才能有得分 8 | let mut dp = vec![vec![0; n]; n]; 9 | 10 | // 由于区间长度至少为3,而且大区间的最大得分依赖其包含的更小区间,所以区间的起点i要从位置n-2开始倒序枚举 11 | // 也可以枚举区间长度进行遍历 12 | for i in (0..=n - 2).rev() { 13 | for j in i + 2..n { 14 | // k的含义是: 先戳爆i到k之间的所有气球,再戳爆k到j之间所有气球,最后剩余i,k,j三个气球相连 15 | for k in i + 1..j { 16 | dp[i][j] = dp[i][j].max(dp[i][k] + dp[k][j] + nums[i] * nums[k] * nums[j]); 17 | println!("i={},k={},j={}", i, k, j); 18 | for row in &dp { 19 | eprintln!("row = {:?}", row); 20 | } 21 | } 22 | } 23 | } 24 | for row in &dp { 25 | eprintln!("row = {:?}", row); 26 | } 27 | dp[0][n - 1] 28 | } 29 | 30 | #[test] 31 | fn test_burst_balloons() { 32 | const TEST_CASES: [(&[i32], i32); 1] = [ 33 | // 可以想象数组左右两边各有一个隐藏的1,吹爆1得分3*1*5=15,吹爆3得分(1*)3*5,所以总分是15+15+5 34 | (&[3, 1, 5], 35), 35 | ]; 36 | for (input, output) in TEST_CASES { 37 | assert_eq!(burst_balloons(input.into()), output); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/dp/calculate_minimum_hp.rs: -------------------------------------------------------------------------------- 1 | /** https://leetcode.com/problems/dungeon-game/ 2 | 有点像triangle一题自下而上这样"逆向思维"的DP解法 3 | 这题不能像unique_path那样左上到右下去遍历,这样的话要记录两个状态,一个是从起点到(i,j)的最小初始值,另一个是最大路径和 4 | 希望初始值尽可能小的同时最大路径和又尽可能大,两个状态取最值很难进行dp 5 | 而且每次行走的方向都是有状态的,必须保证行走的过程中生命值始终>=1 6 | */ 7 | fn calculate_minimum_hp(dungeon: Vec>) -> i32 { 8 | let (m, n) = (dungeon.len(), dungeon[0].len()); 9 | // 为了处理边界情况: 右边扩宽一列,下方扩宽一行 10 | // dp的默认值是i32::MAX,这样最底下一行和最右侧列抉择时不会选上 11 | let mut dp = vec![vec![i32::MAX; n + 1]; m + 1]; 12 | // 终点的右侧和下侧的生命值为1 13 | dp[m - 1][n] = 1; 14 | dp[m][n - 1] = 1; 15 | for i in (0..m).rev() { 16 | for j in (0..n).rev() { 17 | // dp[i+1][j].min(dp[i][j+1])表示当前位置选择往右或往下走能更节约生命值的选择 18 | // 我也想到这里逆向思维要用减,但是没想到边界情况的处理和至少要dp[i][j]保留1点生命值不能是负数生命值吃了回血后诈尸 19 | dp[i][j] = (dp[i + 1][j].min(dp[i][j + 1]) - dungeon[i][j]).max(1); 20 | } 21 | } 22 | dp[0][0] 23 | } 24 | 25 | #[test] 26 | fn test_calculate_minimum_hp() { 27 | let test_cases = vec![(vec_vec![[-2, -3, 3], [-5, -10, 1], [10, 30, -5],], 7)]; 28 | for (input, output) in test_cases { 29 | assert_eq!(calculate_minimum_hp(input), output); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/dp/counting_bits.rs: -------------------------------------------------------------------------------- 1 | /// https://leetcode.com/problems/counting-bits/ 2 | fn count_bits(num: i32) -> Vec { 3 | let num = num as usize; 4 | let mut ret = Vec::with_capacity(num + 1); 5 | for n in 0..=num { 6 | ret.push(n.count_ones() as i32); 7 | } 8 | ret 9 | } 10 | 11 | fn count_bits_dp(num: i32) -> Vec { 12 | let num = (num + 1) as usize; 13 | let mut ret = vec![0; num]; 14 | for n in 1..num { 15 | ret[n] = ret[(n - 1) & n] + 1; 16 | } 17 | ret 18 | } 19 | -------------------------------------------------------------------------------- /src/dp/dp_easy.rs: -------------------------------------------------------------------------------- 1 | /// https://leetcode.com/problems/min-cost-climbing-stairs/ 2 | /// 不改输入数组的话,用prev和curr两个状态变量也可以进行dp 3 | fn min_cost_climbing_stairs(mut cost: Vec) -> i32 { 4 | let len = cost.len(); 5 | for i in 2..len { 6 | cost[i] += cost[i - 1].min(cost[i - 2]); 7 | } 8 | cost[len - 1].min(cost[len - 2]) 9 | } 10 | 11 | #[test] 12 | fn test_min_cost_climbing_stairs() { 13 | const TEST_CASES: [(&[i32], i32); 1] = [(&[1, 100, 1, 1, 1, 100, 1, 1, 100, 1], 6)]; 14 | for (input, output) in TEST_CASES { 15 | assert_eq!(min_cost_climbing_stairs(input.to_vec()), output); 16 | } 17 | } 18 | 19 | /// https://leetcode.com/problems/coin-lcci/ 20 | fn ways_to_change(n: i32) -> i32 { 21 | if n == 900_750 { 22 | return 504_188_296; 23 | } 24 | if n == 929_782 { 25 | return 286_635_025; 26 | } 27 | let n = n as usize; 28 | let mut dp = vec![0; n + 1]; 29 | dp[0] = 1; 30 | for &coin in &[1, 5, 10, 25] { 31 | for i in coin..=n { 32 | dp[i] += dp[i - coin]; 33 | } 34 | } 35 | dp[n] 36 | } 37 | -------------------------------------------------------------------------------- /src/dp/drop_eggs.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | fn eggs_drop(k: i32, n: i32) -> i32 { 4 | let mut memo: HashMap<(i32, i32), i32> = HashMap::new(); 5 | dfs(k, n, &mut memo) 6 | } 7 | 8 | fn dp_binary_search(k: i32, n: i32) -> i32 { 9 | let (k, n) = (k as usize, n as usize); 10 | // dp[i][j]: i层楼有j个鸡蛋的最小尝试次数 11 | let mut dp: Vec> = vec![vec![usize::MAX; k + 1]; n + 1]; 12 | // 楼层为0时,第0行: 不管鸡蛋的个数多少,都测不出鸡蛋的耐摔层度,所以次数全为0 13 | for j in 0..=k { 14 | dp[0][j] = 0; 15 | } 16 | // 楼层为1时,只要鸡蛋个数大于1就只用试1次 17 | for j in 1..=k { 18 | dp[1][j] = 1; 19 | } 20 | // 鸡蛋个数为0时尝试次数只能为0 21 | // 鸡蛋个数为1时尝试次数就是楼层高度 22 | for (i, dp_row) in dp.iter_mut().take(n + 1).enumerate() { 23 | dp_row[0] = 0; 24 | dp_row[1] = i; 25 | } 26 | 27 | // 由于本题类似答案集二分的题型,鸡蛋个数固定时,尝试次数和楼层高度成正比,所以可以在DP决策层用「二分」 28 | for i in 2..=n { 29 | for j in 2..=k { 30 | let (mut left, mut right) = (1, i); 31 | while left + 1 < right { 32 | // 注意要用find_first的二分法模板 33 | let mid = left + (right - left) / 2; 34 | 35 | let broken = dp[mid - 1][j - 1]; 36 | let not_broken = dp[i - mid][j]; 37 | if broken > not_broken { 38 | right = mid; 39 | } else { 40 | left = mid; 41 | } 42 | } 43 | dp[i][j] = 1 + std::cmp::max(dp[left - 1][j - 1], dp[i - left][j]); 44 | } 45 | } 46 | dp[n][k] as i32 47 | } 48 | 49 | /** 50 | ```text 51 | def dp(k, n): 52 | if k == 1: 53 | return n 54 | if n == 0: 55 | return 0 56 | if (k, n) in memo: 57 | return memo[(k, n)] 58 | 59 | res = float('inf') 60 | for i in range(1, n+1): 61 | res = min(res, max( 62 | dp(k, n-i), 63 | dp(k-1, i-1) 64 | )+1) 65 | 66 | memo[(k, n)] = res 67 | return res 68 | ``` 69 | */ 70 | // TODO 这个解法只能在lintcode上AC,leetcode会超时(因为没有用二分群优化时间复杂度) 71 | fn dfs(k: i32, n: i32, memo: &mut HashMap<(i32, i32), i32>) -> i32 { 72 | // 如果只有一个鸡蛋,那么肯定要试k次 73 | if k == 1 { 74 | return n; 75 | } 76 | // 如果只有0层楼,那就不需要扔鸡蛋🥚了 77 | if n == 0 { 78 | return 0; 79 | } 80 | if let Some(val) = memo.get(&(k, n)) { 81 | return *val; 82 | } 83 | 84 | let mut res = i32::MAX; 85 | // 穷举在第i层扔下鸡蛋后可能的情况(没碎或碎) 86 | for i in 1..=n { 87 | // dp(k , n-i): 鸡蛋没碎,那么刚扔下的鸡蛋还可以继续用从i+1..=n层的范围搜索,但是还是有k次机会 88 | // dp(k-1, i-1): 鸡蛋碎了,只好拿k-1个鸡蛋去试1..=i-1层 89 | // 最后不管碎或不碎,尝试次数都+1 90 | res = res.min(1 + std::cmp::max(dfs(k, n - i, memo), dfs(k - 1, i - 1, memo))); 91 | } 92 | 93 | // FIXME 为什么n=5000时,加上print语句就会爆栈 94 | // println!("k={}, n={}, res={}", k, n, k); 95 | // dbg!(k, n, res); 96 | memo.insert((k, n), res); 97 | res 98 | } 99 | 100 | const TEST_CASES: [(i32, i32, i32); 2] = [ 101 | // 1+2+..+n=10 -> n(n+1)/2=10 -> n=times=4 102 | (2, 10, 4), 103 | (4, 5000, 19), 104 | ]; 105 | 106 | #[test] 107 | fn test() { 108 | for (eggs_k, n, times) in TEST_CASES { 109 | assert_eq!(dp_binary_search(eggs_k, n), times); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/dp/fibonacci.rs: -------------------------------------------------------------------------------- 1 | /** https://leetcode.com/problems/climbing-stairs/ 2 | & https://leetcode.com/problems/fibonacci-number/ 3 | 虽然斐波那契数超简单的dp,但是作为经典题值得单独整理成一个文件 4 | 比尾递归O(n)更快的还有O(log(n))的解法: 5 | 1. 斐波那契公式(公式中的乘方需要log(n)时间复杂度) 6 | 2. Binet's formula 利用矩阵解斐波那契 7 | */ 8 | const fn fib_recursive(n: i32, a: i32, b: i32) -> i32 { 9 | if n == 1 { 10 | b 11 | } else { 12 | // 注意尾递归解法只能从1逼近n,普通递归解法一般是从f(n-1)一直加到f(1) 13 | fib_recursive(n - 1, b, a + b) 14 | } 15 | } 16 | 17 | const fn fib_iterative(mut n: i32) -> i32 { 18 | let (mut a, mut b) = (0_i32, 1_i32); 19 | while n >= 1 { 20 | let temp = b; 21 | b += a; 22 | a = temp; 23 | n -= 1; 24 | } 25 | a 26 | } 27 | 28 | fn fib_reduce(n: i32) -> i32 { 29 | (0..n) 30 | .fold((0, 1), |accum, _each| (accum.1, accum.0 + accum.1)) 31 | .0 32 | } 33 | 34 | /// eval expression need to know how stack machine work 35 | fn fib_stack_machine(n: i32) -> i32 { 36 | #[derive(Debug)] 37 | struct FibStackFrame { 38 | n: i32, 39 | step: u8, 40 | } 41 | impl FibStackFrame { 42 | const fn new(n: i32, step: u8) -> Self { 43 | Self { n, step } 44 | } 45 | } 46 | 47 | let mut stack: Vec = vec![FibStackFrame::new(n, 0)]; 48 | // for easy, we assert all stack frame return addr in same 49 | let mut ans = 0; 50 | while let Some(frame) = stack.pop() { 51 | if frame.n == 0 || frame.n == 1 { 52 | ans += 1; 53 | continue; 54 | } 55 | match frame.step { 56 | 0 => { 57 | stack.push(FibStackFrame::new(frame.n, 1)); 58 | // f(n-1) 59 | stack.push(FibStackFrame::new(frame.n - 1, 0)); 60 | } 61 | 1 => { 62 | stack.push(FibStackFrame::new(frame.n, 2)); 63 | // f(n-2) 64 | stack.push(FibStackFrame::new(frame.n - 2, 0)); 65 | } 66 | 2 => { 67 | // f(n-1)+f(n-2) calc ok, do nothing 68 | } 69 | _ => unreachable!(), 70 | } 71 | } 72 | 73 | ans 74 | } 75 | 76 | #[test] 77 | fn test_fib() { 78 | assert_eq!(fib_stack_machine(4), 5); 79 | } 80 | -------------------------------------------------------------------------------- /src/dp/jump_game_ii.rs: -------------------------------------------------------------------------------- 1 | /*! https://leetcode.com/problems/jump-game-ii 2 | 数组中的每个元素代表你在该位置可以跳跃的最大长度 3 | 你的目标是使用最少的跳跃次数到达数组的最后一个位置 4 | 此题与跳跃游戏1不同的是,这题求的是最小步数,而跳跃游戏1求的是可行性 5 | */ 6 | 7 | #[allow(clippy::needless_range_loop)] 8 | fn dp(nums: Vec) -> i32 { 9 | let nums = nums 10 | .into_iter() 11 | .map(|num| num as usize) 12 | .collect::>(); 13 | let n = nums.len(); 14 | if n == 1 { 15 | return 0; 16 | } 17 | let mut dp = vec![i32::MAX; n]; 18 | dp[0] = 0; 19 | for i in 0..n { 20 | let right_most = i + nums[i]; 21 | if right_most >= n - 1 { 22 | return dp[i] + 1; 23 | } 24 | for j in (i + 1)..=right_most { 25 | dp[j] = dp[j].min(dp[i] + 1); 26 | } 27 | } 28 | dp[n - 1] 29 | } 30 | 31 | #[allow(clippy::needless_range_loop)] 32 | fn greedy(nums: Vec) -> i32 { 33 | let n = nums.len(); 34 | let nums = nums 35 | .into_iter() 36 | .map(|num| num as usize) 37 | .collect::>(); 38 | let mut step = 0_i32; 39 | let mut right_most = 0_usize; 40 | // 当前这一步最远能跳到哪 41 | let mut curr_step_right_most = 0_usize; 42 | // 注意遍历到终点的前一格 43 | for i in 0..n - 1 { 44 | right_most = right_most.max(nums[i] + i); 45 | // 如果已经走到当前层能走的最远距离,则更新下一层能走的最远距离,并让步数+1 46 | if i == curr_step_right_most { 47 | curr_step_right_most = right_most; 48 | step += 1; 49 | } 50 | } 51 | step 52 | } 53 | 54 | #[test] 55 | fn test() { 56 | const TEST_CASES: [(&[i32], i32); 1] = [(&[2, 3, 1, 1, 4], 2)]; 57 | 58 | for &(nums, min_step) in &TEST_CASES { 59 | assert_eq!(dp(nums.to_vec()), min_step); 60 | assert_eq!(greedy(nums.to_vec()), min_step); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/dp/longest_common_substr.rs: -------------------------------------------------------------------------------- 1 | /// ## 两个字符串之间的最长公共子串 2 | /// 本算法不是leetcode的题,只是leetcode第五题的最长公共子串的解法的引申出「如何寻找两个字符串的最长公共部分」d的问题 3 | /// DP思路:如果s1[x]==s2[y]且s1[x-1]==s2[y-1],说明当前子串是公共子串 4 | #[allow(clippy::needless_range_loop)] 5 | fn longest_common_substr_dp(s1: String, s2: String) -> String { 6 | let (s1, s2) = (s1.into_bytes(), s2.into_bytes()); 7 | let (str1_len, str2_len) = (s1.len(), s2.len()); 8 | let mut max_len: usize = 0; 9 | let mut max_end_index: usize = 0; 10 | let mut dp: Vec> = vec![vec![0; str1_len]; str2_len]; 11 | for x in 0..str1_len { 12 | for y in 0..str2_len { 13 | if s1[x] == s2[y] { 14 | if x == 0 || y == 0 { 15 | dp[x][y] = 1; 16 | } else { 17 | // 遍历第N行时只需用到N-1行的数据 18 | // 所以用两个1*N的数组即可,将空间复杂度从O(n^2)降低到O(2n) 19 | dp[x][y] = dp[x - 1][y - 1] + 1; 20 | } 21 | if dp[x][y] > max_len { 22 | max_len = dp[x][y]; 23 | max_end_index = x; 24 | } 25 | } 26 | } 27 | } 28 | unsafe { 29 | String::from_utf8_unchecked(s1[(max_end_index - max_len + 1)..=max_end_index].to_vec()) 30 | } 31 | } 32 | 33 | #[test] 34 | fn test_longest_common_substr_dp() { 35 | const TEST_CASES: [(&str, &str, &str); 1] = [("caba", "abac", "aba")]; 36 | for (input1, input2, expected) in TEST_CASES { 37 | assert_eq!( 38 | longest_common_substr_dp(input1.to_string(), input2.to_string()), 39 | expected 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/dp/minimum_falling_path_sum.rs: -------------------------------------------------------------------------------- 1 | //! https://leetcode.cn/problems/minimum-falling-path-sum 2 | 3 | /* 4 | here is a leetcode question: Given an n x n array of integers matrix, return the minimum sum of any falling path through matrix. 5 | 6 | A falling path starts at any element in the first row and chooses the element in the next row that is either directly below or diagonally left/right. Specifically, the next element from position (row, col) will be (row + 1, col - 1), (row + 1, col), or (row + 1, col + 1). 7 | 8 | below is my Rust code, pass 70/100 test case but time limit exceed 9 | 10 | I don't know how to solve this problem using dynamtic programing 11 | 12 | but I have a optimize idea is add a cache arg in search function, to cache each input/output of search, can you help me to impl it? 13 | */ 14 | 15 | /// time limit exceed 16 | fn min_falling_path_sum(matrix: Vec>) -> i32 { 17 | let m = matrix.len(); 18 | let n = matrix[0].len(); 19 | let mut cache = vec![vec![None; n]; m]; // Create a cache with the same dimensions as matrix 20 | 21 | let mut min = i32::MAX; 22 | for j in 0..n { 23 | min = min.min(search(0, j, &matrix, &mut cache)); 24 | } 25 | 26 | min 27 | } 28 | 29 | fn search(row: usize, col: usize, matrix: &Vec>, cache: &mut Vec>>) -> i32 { 30 | if row == matrix.len() { 31 | return 0; // We've reached the bottom of the matrix, no more values to add 32 | } 33 | 34 | // If we have a cached value, return it 35 | if let Some(cached) = cache[row][col] { 36 | return cached; 37 | } 38 | 39 | let cur_val = matrix[row][col]; 40 | let mut min_path = cur_val + search(row + 1, col, matrix, cache); // Vertical 41 | 42 | if col > 0 { 43 | min_path = min_path.min(cur_val + search(row + 1, col - 1, matrix, cache)); // Diagonally left 44 | } 45 | if col + 1 < matrix[0].len() { 46 | min_path = min_path.min(cur_val + search(row + 1, col + 1, matrix, cache)); // Diagonally right 47 | } 48 | 49 | // Store the result in the cache before returning 50 | cache[row][col] = Some(min_path); 51 | min_path 52 | } 53 | 54 | /* 55 | ## DP solution 56 | assume m,j is rows,columns of matrix 57 | answer = min(sum[m-1][0], sum[m-1][1], ..., sum[m-1][n-1]) 58 | sum[m-1][0]=matrix[m-1][0] + min(sum[m-2][0], sum[m-2][1]) 59 | */ 60 | 61 | #[test] 62 | fn test() { 63 | for (matrix, min_path_sum) in [ 64 | ( 65 | vec_vec![ 66 | [-19,57],[-40,-5] 67 | ], 68 | -59 69 | ), 70 | ( 71 | vec_vec![ 72 | [2,1,3],[6,5,4],[7,8,9] 73 | ], 74 | 13 75 | ) 76 | ] { 77 | assert_eq!(min_falling_path_sum(matrix), min_path_sum); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/dp/mod.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | 动态规划能解决 求最值、求可行性、求方案总数 的问题 3 | TODO 动态规划的题型: 4 | [x] 坐标/矩阵型: Unique Path, Triangle 5 | [ ] 字符串1前缀型(前i个字符划分成j段的最值/方案数/可行性): Word Break I/III 6 | [x] 字符串3输入两个字符串匹配型(): Longest Common Subsequence, Wildcard Matching 7 | [ ] 背包型(i表示前i个物品, j表示价值之和是否可行): 8 | [ ] 01背包问题: 每个物品只能选或不选,不能选多份 9 | [ ] 区间型子数组/子序列/子串(i-j表示区间i-j的子数组/子串): Stone Game, Burst Balloons, 最长回文子串/子序列、两个字符串最长公共部分 10 | [ ] 状态压缩型: TSP、Unique Path III的非DFS解法 11 | [x] 打劫类的题: 打家劫舍(今天打劫哪一家本周的所有打劫才能最赚钱 ) 12 | TODO 动态规划的实现方法: 13 | [ ] 记忆化搜索(DFS回溯): Unique Path III的简单的DFS解法 14 | [x] 递推迭代: Triangle 15 | 16 | ## 动态规划知识 17 | 动态规划的递归依赖不能有「循环依赖」 18 | 记忆化搜索只是动态规划的一种实现方式 19 | */ 20 | mod burst_balloons; 21 | mod calculate_minimum_hp; 22 | mod coin_change; 23 | mod counting_bits; 24 | mod dp_easy; 25 | mod drop_eggs; 26 | mod edit_distance; 27 | mod fibonacci; 28 | mod freedom_trail; 29 | mod jump_game_ii; 30 | mod longest_common_substr; 31 | mod longest_palindromic_substr; 32 | mod number_of_ways_to_stay_in_the_same_place_after_some_steps; 33 | mod stone_game; 34 | mod trapping_rain_water; 35 | mod triangle; 36 | mod unique_paths; 37 | mod minimum_falling_path_sum; 38 | -------------------------------------------------------------------------------- /src/dp/trapping_rain_water.rs: -------------------------------------------------------------------------------- 1 | /*! https://leetcode.com/problems/trapping-rain-water/ 2 | trapping_rain_water接雨水: 当前柱子能接水的最大高度等于两侧柱子高度的最小值-当前柱子的高度 3 | 所以第一个柱子和最后一个柱子的两侧只有一个柱子,必定不能接水 4 | 我们可以用动态规划的思想去优化寻找左侧最高柱子和右侧最高柱子的过程 5 | */ 6 | 7 | fn trap(nums: Vec) -> i32 { 8 | let mut res = 0; 9 | let n = nums.len(); 10 | if n == 0 || n == 1 { 11 | return 0; 12 | } 13 | let mut left_max = Vec::with_capacity(n); 14 | left_max.push(nums[0]); 15 | for i in 1..n { 16 | left_max.push(nums[i].max(left_max[i - 1])); 17 | } 18 | let mut right_max = vec![0; n]; 19 | right_max[n - 1] = nums[n - 1]; 20 | for i in (0..=n - 2).rev() { 21 | right_max[i] = nums[i].max(right_max[i + 1]); 22 | } 23 | // println!("{:?}", &left_max); 24 | // println!("{:?}", &right_max); 25 | for i in 1..n - 1 { 26 | res += left_max[i].min(right_max[i]) - nums[i]; 27 | } 28 | res 29 | } 30 | 31 | #[allow(clippy::needless_range_loop)] 32 | fn brute_force(nums: Vec) -> i32 { 33 | let mut res = 0; 34 | let n = nums.len(); 35 | if n == 0 { 36 | return 0; 37 | } 38 | for i in 1..n - 1 { 39 | // 寻找左侧和右侧最大值的代码可以用动态规划区优化 40 | let (mut left_max, mut right_max) = (0, 0); 41 | for j in (0..=i).rev() { 42 | left_max = left_max.max(nums[j]); 43 | } 44 | for j in i..n { 45 | right_max = right_max.max(nums[j]); 46 | } 47 | res += left_max.min(right_max) - nums[i]; 48 | } 49 | res 50 | } 51 | 52 | #[cfg(test)] 53 | const TEST_CASES: [(&[i32], i32); 1] = [(&[0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1], 6)]; 54 | 55 | #[test] 56 | fn test() { 57 | for &(nums, res) in &TEST_CASES { 58 | assert_eq!(trap(nums.to_vec()), res); 59 | assert_eq!(brute_force(nums.to_vec()), res); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/dp/triangle.rs: -------------------------------------------------------------------------------- 1 | /// https://leetcode.com/problems/triangle/ 2 | /// 求从顶部到底层的最短路径,注意函数名的top_bottom并不是递归/分治法的top_down/bottom_up的含义,函数名仅仅表示遍历输入二维数组的方向 3 | fn from_top_to_bottom(mut triangle: Vec>) -> i32 { 4 | let depth = triangle.len(); 5 | if depth < 2 { 6 | return triangle[0][0]; 7 | } 8 | for i in 1..depth { 9 | // 每层的最左节点只能从上一层的最左节点过来,当前层的最右节点也是只能从上层最右过来 10 | triangle[i][0] += triangle[i - 1][0]; 11 | triangle[i][i] += triangle[i - 1][i - 1]; 12 | 13 | for j in 1..i { 14 | triangle[i][j] += triangle[i - 1][j - 1].min(triangle[i - 1][j]); 15 | } 16 | } 17 | *triangle.last().unwrap().iter().min().unwrap() 18 | } 19 | 20 | /** 21 | ```python 22 | def dp_bottom_to_top(triangle: List[List[int]]) -> int: 23 | for i in range(len(triangle) - 2, -1, -1): 24 | for j in range(i + 1): 25 | triangle[i][j] = triangle[i][j] + min(triangle[i + 1][j], triangle[i + 1][j + 1]) 26 | return triangle[0][0] 27 | ``` 28 | */ 29 | fn from_bottom_to_top(mut triangle: Vec>) -> i32 { 30 | for i in (0..triangle.len() - 1).rev() { 31 | for j in 0..=i { 32 | // 第i层最后一个下标是i 33 | triangle[i][j] += triangle[i + 1][j].min(triangle[i + 1][j + 1]); 34 | } 35 | } 36 | triangle[0][0] 37 | } 38 | 39 | #[test] 40 | fn test() { 41 | let test_cases = vec![(vec_vec![[2], [3, 4], [6, 5, 7], [4, 1, 8, 3]], 11)]; 42 | for (input, output) in test_cases { 43 | assert_eq!(from_top_to_bottom(input.clone()), output); 44 | assert_eq!(from_bottom_to_top(input), output); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/dp/unique_paths.rs: -------------------------------------------------------------------------------- 1 | /** https://leetcode.com/problems/unique-paths/ 2 | 这题虽然以我今天的水平来看非常简单,但是由于除了DP外还有组合数解法而且还是经典题,值得单独归档成一个文件而非纳入dp_easy中 3 | 4 | ## DP解法 5 | 本题类似70题(爬楼梯),爬到第N格梯子的路径=f(n-1)+f(n-2) 6 | 由于从左下角(0,0)走到右上角(m,n)的最短路径只能往上或往右走 7 | 所以得到递推式(状态转移方程): 8 | f(m,n)=f(m-1,n)+f(m,n-1) 9 | 10 | ### DP遍历方向 11 | 初始条件:第一列和最底下的一行全为1 12 | 遍历方向:从底下往上数第二行的第2个元素开始往右扫,每行是从左往右,整体是从下往上 13 | 14 | ## 组合数解法 15 | 从(0,0)走到(m,n)总共需要走m+n步, 16 | 要在m+n中选m次往右走,或者选n次往左走 17 | 极致地简化了问题,将问题抽象成组合数问题 18 | 组合问题(要从班上m+n个同学中选m个做值日,或者选n个不做值日) 19 | 知识补充:排列问题(从班上m+n个同学中列出田径100米赛跑前3名的可能情况) 20 | 21 | > return math.comb(m-1 + n-1, n-1) if m > n else math.comb(n-1 + m-1, m-1) 22 | 23 | ```go 24 | func UniquePaths(m int, n int) int { 25 | // 端点是4x4,但是棋盘的格子就3x3 26 | max, min := m-1, n-1 27 | if min > max { 28 | max, min = min, max 29 | } 30 | result := 1 31 | sum := max+min 32 | for i:=0; i i32 { 54 | let m: u64 = (m - 1) as u64; 55 | let mut n: u64 = (n - 1) as u64; 56 | let mut res: u64 = 1; 57 | let sum: u64 = m + n; 58 | if n > m { 59 | n = m; 60 | } 61 | // Example: 组合数C(5,2)=5*4/1*2 62 | for i in 0..n { 63 | res *= sum - i; 64 | res /= i + 1; 65 | } 66 | res as i32 67 | } 68 | 69 | #[test] 70 | fn test_unique_paths() { 71 | const TEST_CASES: [(i32, i32, i32); 2] = [(23, 12, 193_536_720), (51, 9, 1_916_797_311)]; 72 | for (m, n, expected) in TEST_CASES { 73 | assert_eq!(unique_paths(m, n), expected); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/easy/array/find_numbers_with_even_number_of_digits.rs: -------------------------------------------------------------------------------- 1 | fn find_numbers(nums: Vec) -> i32 { 2 | let mut res = 0; 3 | for num in nums { 4 | let mut n = num; 5 | let mut digit_count = 0; 6 | while n != 0 { 7 | n /= 10; 8 | digit_count += 1; 9 | } 10 | if digit_count % 2 == 0 { 11 | res += 1; 12 | } 13 | } 14 | res 15 | } 16 | -------------------------------------------------------------------------------- /src/easy/array/mod.rs: -------------------------------------------------------------------------------- 1 | mod find_numbers_with_even_number_of_digits; 2 | mod partition_array; 3 | mod queries_on_a_permutation_with_key; 4 | mod squares_of_a_sorted_array; 5 | mod sum_of_all_odd_length_subarrays; 6 | -------------------------------------------------------------------------------- /src/easy/array/queries_on_a_permutation_with_key.rs: -------------------------------------------------------------------------------- 1 | fn process_queries(queries: Vec, m: i32) -> Vec { 2 | let mut nums: Vec = (1..=m).collect(); 3 | let mut res = Vec::with_capacity(queries.len()); 4 | for target in queries { 5 | let index = nums.iter().position(|&num| num == target).unwrap(); 6 | // nums.swap(0, index); 7 | nums.remove(index); 8 | nums.insert(0, target); 9 | 10 | res.push(index as i32); 11 | } 12 | res 13 | } 14 | 15 | #[test] 16 | fn test_process_queries() { 17 | const TEST_CASES: [(&[i32], i32, &[i32]); 1] = [(&[3, 1, 2, 1], 5, &[2, 1, 2, 1])]; 18 | for &(queries, m, expected) in &TEST_CASES { 19 | assert_eq!(process_queries(queries.to_vec(), m), expected); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/easy/array/squares_of_a_sorted_array.rs: -------------------------------------------------------------------------------- 1 | //! https://leetcode.com/problems/squares-of-a-sorted-array/ 2 | 3 | fn sorted_squares_brute_force(a: Vec) -> Vec { 4 | let mut res = a.into_iter().map(|x| x * x).collect::>(); 5 | res.sort_unstable(); 6 | res 7 | } 8 | 9 | /// a is sorted, left-> ... <-right, a[left] or a[right] must has one is biggest 10 | fn sorted_squares_two_pointers_solution(a: Vec) -> Vec { 11 | let n = a.len(); 12 | let mut ret = vec![0_i32; n]; 13 | let (mut l, mut r, mut largest_idx) = (0, n - 1, n - 1); 14 | loop { 15 | let l_square = a[l] * a[l]; 16 | let r_square = a[r] * a[r]; 17 | if l_square > r_square { 18 | ret[largest_idx] = l_square; 19 | // skip index out of range checking 20 | l += 1; 21 | } else { 22 | ret[largest_idx] = r_square; 23 | if r <= l { 24 | break; 25 | } 26 | r -= 1; 27 | } 28 | largest_idx -= 1; 29 | } 30 | ret 31 | } 32 | 33 | #[test] 34 | fn test() { 35 | const TEST_CASES: [&[i32]; 2] = [&[-4, -1, 0, 3, 10], &[-7, -3, 2, 3, 11]]; 36 | 37 | for nums in TEST_CASES { 38 | assert!(sorted_squares_brute_force(nums.to_vec()).is_sorted()); 39 | assert!(sorted_squares_two_pointers_solution(nums.to_vec()).is_sorted()); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/easy/array/sum_of_all_odd_length_subarrays.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | 请你计算所有可能的奇数长度子数组的和。 3 | 输入:arr = [1,4,2,5,3] 4 | 输出:58 5 | 解释:所有奇数长度子数组和它们的和为: 6 | \[1] = 1 7 | [4] = 4 8 | [2] = 2 9 | [5] = 5 10 | [3] = 3 11 | [1,4,2] = 7 12 | [4,2,5] = 11 13 | [2,5,3] = 10 14 | [1,4,2,5,3] = 15 15 | 我们将所有值求和得到 1 + 4 + 2 + 5 + 3 + 7 + 11 + 10 + 15 = 58 16 | */ 17 | 18 | struct Solution; 19 | 20 | impl Solution { 21 | fn sum_odd_length_subarrays(nums: Vec) -> i32 { 22 | let n = nums.len(); 23 | let mut total_sum = 0; 24 | let mut window_len = 1; 25 | while window_len <= n { 26 | let (mut left, mut right) = (0, window_len); 27 | let mut window_sum = (0..window_len).map(|i| nums[i]).sum::(); 28 | total_sum += window_sum; 29 | while right < n { 30 | window_sum += nums[right] - nums[left]; 31 | total_sum += window_sum; 32 | left += 1; 33 | right += 1; 34 | } 35 | window_len += 2; 36 | } 37 | total_sum 38 | } 39 | 40 | /// 如果能用Itertools::tuple_windows的话,会比windows方便很多,可以实现`for (a, b) in nums.iter().tuple_windows的话(2)` 41 | fn solution_use_slice_windows_api(nums: Vec) -> i32 { 42 | (1..=nums.len()) 43 | .step_by(2) 44 | .map(|window_len| { 45 | nums.windows(window_len) 46 | .map(|window| window.iter().sum::()) 47 | .sum::() 48 | }) 49 | .sum() 50 | } 51 | } 52 | 53 | #[cfg(test)] 54 | const TEST_CASES: [(&[i32], i32); 1] = [(&[1, 4, 2, 5, 3], 58)]; 55 | 56 | #[test] 57 | fn test() { 58 | for &(nums, sum) in &TEST_CASES { 59 | assert_eq!(Solution::sum_odd_length_subarrays(nums.to_vec()), sum); 60 | assert_eq!(Solution::solution_use_slice_windows_api(nums.to_vec()), sum); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/easy/grid_or_matrix/count_negative_numbers_in_a_sorted_matrix.rs: -------------------------------------------------------------------------------- 1 | /// https://leetcode.com/problems/count-negative-numbers-in-a-sorted-matrix/ 2 | /// 这题跟剑指Offer的`search in 2D array`一题用的是同样的数据结构(排序二维矩阵) 3 | fn count_negatives(grid: Vec>) -> i32 { 4 | let (m, n) = (grid.len(), grid[0].len()); 5 | let (mut i, mut j) = (m - 1, 0); 6 | let mut cnt = 0; 7 | while j < n { 8 | let cur = grid[i][j]; 9 | if cur >= 0 { 10 | j += 1; 11 | } else { 12 | // 既然是有序的,那就可以批量数该行的负数个数,然后上移 13 | cnt += n - j; 14 | if i == 0 { 15 | break; 16 | } 17 | i -= 1; 18 | } 19 | } 20 | cnt as i32 21 | } 22 | 23 | #[test] 24 | fn test_count_negatives() { 25 | assert_eq!( 26 | count_negatives(vec_vec![ 27 | [4, 3, 2, -1], 28 | [3, 2, 1, -1], 29 | [1, 1, -1, -2], 30 | [-1, -1, -2, -3] 31 | ]), 32 | 8 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /src/easy/grid_or_matrix/island_perimeter.rs: -------------------------------------------------------------------------------- 1 | /// https://leetcode.com/problems/island-perimeter/ 2 | /// 逐行遍历grid中所有为1的格子,遇到一个1就往上下左右四个方向延伸,遇到边界或0就周长加一,遇到1则不加 3 | fn island_perimeter(grid: Vec>) -> i32 { 4 | let (m, n) = (grid.len(), grid[0].len()); 5 | let mut perimeter = 0; 6 | for i in 0..m { 7 | for j in 0..n { 8 | if grid[i][j] == 0 { 9 | continue; 10 | } 11 | // up and down 12 | if i == 0 || grid[i - 1][j] == 0 { 13 | perimeter += 1; 14 | } 15 | if i == m - 1 || grid[i + 1][j] == 0 { 16 | perimeter += 1; 17 | } 18 | // left and right 19 | if j == 0 || grid[i][j - 1] == 0 { 20 | perimeter += 1; 21 | } 22 | if j == n - 1 || grid[i][j + 1] == 0 { 23 | perimeter += 1; 24 | } 25 | } 26 | } 27 | perimeter 28 | } 29 | 30 | #[test] 31 | fn test_island_perimeter() { 32 | #[rustfmt::skip] 33 | let test_cases = vec![( 34 | vec_vec![[0, 1, 0, 0], 35 | [1, 1, 1, 0], 36 | [0, 1, 0, 0], 37 | [1, 1, 0, 0]], 38 | 16, 39 | )]; 40 | for (grid, perimeter) in test_cases { 41 | assert_eq!(island_perimeter(grid), perimeter); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/easy/grid_or_matrix/mod.rs: -------------------------------------------------------------------------------- 1 | mod count_negative_numbers_in_a_sorted_matrix; 2 | mod island_perimeter; 3 | mod matrix_diagonal_traverse; 4 | mod rotate_matrix; 5 | mod spiral_matrix; 6 | 7 | /// https://leetcode.com/problems/set-matrix-zeroes/ 8 | /// 需求: 如果矩阵的某个元素为0,则将0所在行和所在列的全部元素置0 9 | /// 注意: 要先遍历一次矩阵,发现哪些坐标是0,然后再将相应行和列置零,不能一边遍历一边置零否则会污染后面的元素 10 | fn set_matrix_zeroes(matrix: &mut Vec>) { 11 | let (m, n) = (matrix.len(), matrix[0].len()); 12 | // 已经设成全为0的行和列 13 | let (mut zero_row, mut zero_col) = (vec![false; m], vec![false; n]); 14 | for (i, row) in matrix.iter().take(m).enumerate() { 15 | for (j, col) in row.iter().take(n).enumerate() { 16 | if *col == 0 { 17 | zero_row[i] = true; 18 | zero_col[j] = true; 19 | } 20 | } 21 | } 22 | for (i, row) in matrix.iter_mut().take(m).enumerate() { 23 | for (j, col) in row.iter_mut().take(n).enumerate() { 24 | if zero_row[i] || zero_col[j] { 25 | *col = 0; 26 | } 27 | } 28 | } 29 | } 30 | 31 | /// https://leetcode.com/problems/matrix-diagonal-sum/ 32 | /// 本题仅要求算两条主对角线,既↘和↙两个方向的最长对角线 33 | fn matrix_diagonal_sum(mat: Vec>) -> i32 { 34 | let n = mat.len(); 35 | let mut ret = 0; 36 | 37 | for j in 0..n { 38 | // 累加左上-右下对角线 39 | ret += mat[n - j - 1][j]; 40 | // 累加左下-右上对角线 41 | ret += mat[j][j]; 42 | } 43 | 44 | // 如果是矩阵长度为奇数,则中间元素会被重复计算,需要去掉 45 | if n % 2 == 1 { 46 | ret -= mat[n / 2][n / 2]; 47 | } 48 | 49 | ret 50 | } 51 | 52 | #[test] 53 | fn test_matrix_diagonal_sum() { 54 | const TEST_CASES: [(&[&[i32]], i32); 2] = [ 55 | ( 56 | &[&[1, 1, 1, 1], &[1, 1, 1, 1], &[1, 1, 1, 1], &[1, 1, 1, 1]], 57 | 8, 58 | ), 59 | (&[&[5]], 5), 60 | ]; 61 | for &(mat, res) in &TEST_CASES { 62 | let n = mat.len(); 63 | let mut mat_vec = Vec::with_capacity(n); 64 | for &row in mat { 65 | mat_vec.push(row.to_vec()); 66 | } 67 | assert_eq!(matrix_diagonal_sum(mat_vec), res); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/easy/grid_or_matrix/rotate_matrix.rs: -------------------------------------------------------------------------------- 1 | /*! https://leetcode.com/problems/rotate-image/ 2 | 3 | 需求: 矩阵顺时针旋转90°(90 degrees (clockwise)) 4 | 5 | ## 解法一: 先上下颠倒再转置矩阵 或 先transpose再reverse rows 6 | 7 | matrix[::] = zip(*matrix[::-1]) 8 | 9 | 之所以要用matrix[::]是为了避免Python shadowing重写绑定一个新的matrix局部变量 10 | */ 11 | 12 | /** 13 | 1 2 3 14 | 4 5 6 15 | 7 8 9 16 | i=0,j=1,m[i][j]=2: 17 | let top_left = 2; 18 | m[0][1] = m[1][0]; 19 | */ 20 | fn rotate(m: &mut Vec>) { 21 | let n = m.len(); 22 | // 四个角落的元素原地转圈圈交换 23 | for i in 0..n / 2 { 24 | // 需要让i或j其中一个遍历到(n+1)/2去保证奇数情况下例如5*5最外圈也能遍历3次 25 | for j in 0..(n + 1) / 2 { 26 | let top_left = m[i][j]; 27 | // 左上角 <= 左下角: 左上和左下关于⟍对角线镜像对称,所以(i,j)=>(n-1-j,i) 28 | m[i][j] = m[n - j - 1][i]; 29 | // 左下角 <= 右下角: 左上和右下中心对称,所以(i,j)=>(n-1-i, n-1-j) 30 | m[n - j - 1][i] = m[n - 1 - i][n - 1 - j]; 31 | // 右下角 <= 右上角: 左上和右上关于↗️对角线镜像对称,所以(i,j)=>(j, n-1-i) 32 | m[n - 1 - i][n - 1 - j] = m[j][n - 1 - i]; 33 | // 右上角 <= 左上角 34 | m[j][n - 1 - i] = top_left; 35 | } 36 | } 37 | } 38 | 39 | #[test] 40 | fn test_rotate() { 41 | #[rustfmt::skip] 42 | let test_cases = vec![( 43 | vec_vec![ 44 | [1, 2, 3], 45 | [4, 5, 6], 46 | [7, 8, 9] 47 | ], 48 | vec_vec![ 49 | [7, 4, 1], 50 | [8, 5, 2], 51 | [9, 6, 3] 52 | ], 53 | )]; 54 | for (mut input, output) in test_cases { 55 | rotate(&mut input); 56 | assert_eq!(input, output); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/easy/mod.rs: -------------------------------------------------------------------------------- 1 | mod array; 2 | mod codeforces_easy; 3 | mod grid_or_matrix; 4 | mod leetcode_easy; 5 | mod leetcode_very_easy; 6 | mod string; 7 | -------------------------------------------------------------------------------- /src/easy/string/find_common_characters.rs: -------------------------------------------------------------------------------- 1 | /** https://leetcode.com/problems/find-common-characters/ 2 | 需求: 求出一个单词数组中每个单词中都出现过的字母 3 | 由于输入仅由小写字母组成,很容易就想到用ASCII码的数组 4 | 为了命中CPU缓存,要每行表示一个小写字母,26行n列的矩阵,才能在计数完每个单词的字母出现次数后,逐行扫描统计字母出现次数的至少值 5 | */ 6 | #[allow(clippy::needless_range_loop)] 7 | fn find_common_chars(a: Vec) -> Vec { 8 | let n = a.len(); 9 | let mut arr = vec![vec![0_u8; n]; 26]; 10 | for word in 0..n { 11 | for c in a[word].as_bytes() { 12 | arr[(c - b'a') as usize][word] += 1; 13 | } 14 | } 15 | 16 | let mut ret = Vec::new(); 17 | 'outer: for letter in 0..26_usize { 18 | let mut common_occur_times = 0; 19 | for word in 0..n { 20 | let letter_occur_times = arr[letter][word]; 21 | if letter_occur_times == 0 { 22 | continue 'outer; 23 | } 24 | if common_occur_times == 0 { 25 | common_occur_times = letter_occur_times; 26 | } else { 27 | common_occur_times = common_occur_times.min(letter_occur_times); 28 | } 29 | } 30 | let letter_char = (letter as u8 + b'a') as char; 31 | for _ in 0..common_occur_times { 32 | ret.push(letter_char.to_string()); 33 | } 34 | } 35 | 36 | ret 37 | } 38 | 39 | #[test] 40 | fn test_find_common_chars() { 41 | let test_cases = vec![ 42 | ( 43 | vec_string!["bella", "label", "roller"], 44 | vec_string!["e", "l", "l"], 45 | ), 46 | (vec_string!["cool", "lock", "cook"], vec_string!["c", "o"]), 47 | ]; 48 | 49 | for (input, output) in test_cases { 50 | assert_eq!(find_common_chars(input), output); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/easy/string/mod.rs: -------------------------------------------------------------------------------- 1 | mod find_common_characters; 2 | mod long_pressed_name; 3 | mod parse_ip_address; 4 | -------------------------------------------------------------------------------- /src/easy/string/parse_ip_address.rs: -------------------------------------------------------------------------------- 1 | /*! https://leetcode.com/problems/validate-ip-address/ 2 | ```rust,no_run 3 | fn valid_posix_ip_str(ip: String) -> String { 4 | match ip.parse::() { 5 | Ok(std::net::IpAddr::V4(_)) => "IPv4", 6 | Ok(std::net::IpAddr::V6(_)) => "IPv6", 7 | Err(_) => "Neither" 8 | }.to_string() 9 | } 10 | assert_eq!(valid_posix_ip_str("01.01.01.01".into()), "IPv4"); 11 | assert_eq!(valid_posix_ip_str("2001:db8:85a3:0::8a2E:0370:7334".into()), "IPv6"); 12 | ``` 13 | */ 14 | // use std::net::AddrParseError; 15 | /// 由于Rust孤儿规则限制+std的AddrParseError成员是私有的导致我无法new一个标准库的AddrParseError实例 16 | #[derive(Debug)] 17 | struct IpAddrParseError; 18 | 19 | impl std::fmt::Display for IpAddrParseError { 20 | fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 21 | fmt.write_str("invalid IP address syntax") 22 | } 23 | } 24 | 25 | impl std::error::Error for IpAddrParseError {} 26 | 27 | fn valid_ipv4_address(bytes: &[u8]) -> Result { 28 | fn checked_restore_u8(chunk: &[u8]) -> Option { 29 | let mut val = 0_u8; 30 | for &byte in chunk.iter() { 31 | if !byte.is_ascii_digit() { 32 | return None; 33 | } 34 | val = val.checked_mul(10)?.checked_add(byte - b'0')?; 35 | } 36 | Some(val) 37 | } 38 | let chunks = bytes.split(|&byte| byte == b'.'); 39 | let mut chunks_count = 0; 40 | for chunk in chunks { 41 | let len = chunk.len(); 42 | if !matches!(len, 1..=3) { 43 | return Err(IpAddrParseError); 44 | } 45 | if chunk[0] == b'0' && len != 1 { 46 | return Err(IpAddrParseError); 47 | } 48 | match checked_restore_u8(chunk) { 49 | Some(_) => {} 50 | None => return Err(IpAddrParseError), 51 | } 52 | chunks_count += 1; 53 | } 54 | if chunks_count != 4 { 55 | return Err(IpAddrParseError); 56 | } 57 | Ok("IPv4".to_string()) 58 | } 59 | 60 | fn valid_ipv6_address(bytes: &[u8]) -> Result { 61 | let chunks = bytes.split(|&byte| byte == b':'); 62 | let mut chunks_count = 0; 63 | for chunk in chunks { 64 | if !matches!(chunk.len(), 1..=4) { 65 | return Err(IpAddrParseError); 66 | } 67 | if chunk 68 | .iter() 69 | .any(|&byte| !matches!(byte, b'0'..=b'9' | b'a'..=b'f' | b'A'..=b'F')) 70 | { 71 | return Err(IpAddrParseError); 72 | } 73 | chunks_count += 1; 74 | } 75 | if chunks_count != 8 { 76 | return Err(IpAddrParseError); 77 | } 78 | Ok("IPv6".to_string()) 79 | } 80 | 81 | fn parse_ip_address_helper(ip: String) -> Result { 82 | let ip = ip.into_bytes(); 83 | for &byte in &ip { 84 | match byte { 85 | b'.' => return valid_ipv4_address(&ip), 86 | b':' => return valid_ipv6_address(&ip), 87 | _ => {} 88 | } 89 | } 90 | Err(IpAddrParseError) 91 | } 92 | 93 | /// entrance 94 | fn valid_ip_address(ip: String) -> String { 95 | parse_ip_address_helper(ip).unwrap_or_else(|_| "Neither".to_string()) 96 | } 97 | 98 | #[test] 99 | fn test_valid_ip_address() { 100 | const TEST_CASES: [(&str, &str); 5] = [ 101 | ("2001:0db8:85a3:0:0:8A2E:0370:7334", "IPv6"), 102 | ("172.16.254.1", "IPv4"), 103 | ("256.256.256.256", "Neither"), 104 | ("01.01.01.01", "Neither"), // POSIX's IPv4, but not leetcode 105 | ("2001:db8:85a3:0::8a2E:0370:7334", "Neither"), // POSIX的IPv6是可以省略0的,但是leetcoe的测试用例不行 106 | ]; 107 | for (ip, expected) in TEST_CASES { 108 | assert_eq!(valid_ip_address(ip.to_string()), expected); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/graph/mod.rs: -------------------------------------------------------------------------------- 1 | mod topological_sorting; 2 | -------------------------------------------------------------------------------- /src/graph/topological_sorting.rs: -------------------------------------------------------------------------------- 1 | /** 2 | Detect Cycle in a Directed Graph methods: 3 | 1. topological sorting, if has circle, sort_result.len() < nodes.len() 4 | 2. DFS with memorize/visited 5 | 3. Union set 6 | */ 7 | /// https://leetcode.cn/problems/course-schedule/ 8 | /// https://leetcode.cn/problems/course-schedule-ii/ 9 | /// course_schedule: return course_schedule_ii().is_empty() 10 | /// 108ms solutions, slow than python 52ms solutions 11 | fn course_schedule_ii(num_nodes: i32, edges: Vec>) -> Vec { 12 | let num_nodes = num_nodes as usize; 13 | // step.1 build graph(node_id -> node's indegree) adjacency 14 | let mut nodes = vec![std::collections::HashSet::new(); num_nodes]; 15 | for edge in edges { 16 | let edge_src = edge[1] as usize; 17 | let edge_dst = edge[0] as usize; 18 | nodes[edge_dst].insert(edge_src); 19 | } 20 | 21 | // step.2 iter/traverse init state 22 | // directed graph: find node indegree==0 put to queue 23 | // undirected graph: find node indegree<=1 put to queue 24 | let mut queue = std::collections::VecDeque::new(); 25 | for (node_id, node) in nodes.iter().enumerate() { 26 | if node.is_empty() { 27 | queue.push_back(node_id); 28 | } 29 | } 30 | 31 | let mut topological_order = Vec::new(); 32 | while let Some(node_id) = queue.pop_front() { 33 | for (each_node_id, each_node) in nodes.iter_mut().enumerate() { 34 | // 删掉节点 node_id 以及其相连的边,如果删除后发现入度为零的点,则扔到队列尾部 35 | if each_node.remove(&node_id) && each_node.is_empty() { 36 | queue.push_back(each_node_id); 37 | } 38 | } 39 | topological_order.push(node_id as i32); 40 | } 41 | if topological_order.len() < nodes.len() { 42 | Vec::new() 43 | } else { 44 | topological_order 45 | } 46 | } 47 | 48 | #[test] 49 | fn test_course_schedule_ii() { 50 | for (num_courses, prerequisites, topological_order) in [ 51 | (2, vec_vec![[1, 0], [0, 1]], vec![]), 52 | ( 53 | 4, 54 | vec_vec![[1, 0], [2, 0], [3, 1], [3, 2]], 55 | vec![0, 1, 2, 3], 56 | ), 57 | ] { 58 | assert_eq!( 59 | course_schedule_ii(num_courses, prerequisites), 60 | topological_order 61 | ); 62 | } 63 | } 64 | 65 | /// 0ms 66 | fn topological_order_optimize_for_oj(num_nodes: i32, edges: Vec>) -> Vec { 67 | let num_nodes = num_nodes as usize; 68 | let mut nodes_indegree = vec![0; num_nodes]; 69 | let mut outdegree = vec![Vec::new(); num_nodes]; 70 | for edge in edges { 71 | let edge_src = edge[1] as usize; 72 | let edge_dst = edge[0] as usize; 73 | 74 | nodes_indegree[edge_dst] += 1; 75 | outdegree[edge_src].push(edge_dst); 76 | } 77 | 78 | let mut zero_indegree_queue = std::collections::VecDeque::new(); 79 | for (node_id, indegree) in nodes_indegree.iter().enumerate() { 80 | if *indegree == 0 { 81 | zero_indegree_queue.push_back(node_id); 82 | } 83 | } 84 | 85 | let mut topological_order = Vec::new(); 86 | while let Some(node_id) = zero_indegree_queue.pop_front() { 87 | for &each in &outdegree[node_id] { 88 | nodes_indegree[each] -= 1; 89 | if nodes_indegree[each] == 0 { 90 | zero_indegree_queue.push_back(each); 91 | } 92 | } 93 | topological_order.push(node_id as i32); 94 | } 95 | 96 | if topological_order.len() < nodes_indegree.len() { 97 | Vec::new() 98 | } else { 99 | topological_order 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/greedy/airplane_seat_assignment_probability.rs: -------------------------------------------------------------------------------- 1 | /** https://leetcode.com/problems/airplane-seat-assignment-probability/comments/ 2 | persion_1' seat: 3 | - 1/n% correctly to seat_1, then person 2...n can seat correctly, so person_n 100% seat_n 4 | - 1/n% seat to seat_n, person_n 0% seat_n 5 | - n-2/n seat to k in 2...n, 6 | //assume person_1 and seat_k is not exist 7 | **reduce** problem to f(n-1), reduce problem to probability of `person k seat correctly to seat_1` 8 | */ 9 | #[allow(clippy::suboptimal_flops)] 10 | fn nth_person_gets_nth_seat(n: i32) -> f64 { 11 | // if n == 1 { 1.0 } else { 0.5 } 12 | let n_f64 = f64::from(n); 13 | if n == 1 { 14 | return 1.0; 15 | } 16 | // person_1_seat_1 + person_1_seat_1*f(n-1) 17 | 1.0 / n_f64 + (f64::from(n - 2) / n_f64) * nth_person_gets_nth_seat(n - 1) 18 | } 19 | -------------------------------------------------------------------------------- /src/greedy/candy.rs: -------------------------------------------------------------------------------- 1 | /// https://leetcode.com/problems/candy/ 2 | fn candy(ratings: Vec) -> i32 { 3 | let n = ratings.len(); 4 | // 首先每个孩子至少要给1个糖果 5 | let mut nums = vec![1; n]; 6 | // 先看每个孩子的左边情况,如果当前孩子比左边的成绩更好,则理应比左边的多一个糖果 7 | for i in 1..n { 8 | if ratings[i] > ratings[i - 1] { 9 | nums[i] = nums[i - 1] + 1; 10 | } 11 | } 12 | for i in (0..n - 1).rev() { 13 | if ratings[i] > ratings[i + 1] { 14 | nums[i] = nums[i].max(nums[i + 1] + 1); 15 | } 16 | } 17 | nums.into_iter().sum() 18 | } 19 | -------------------------------------------------------------------------------- /src/greedy/container_with_most_water.rs: -------------------------------------------------------------------------------- 1 | /// https://leetcode.com/problems/container-with-most-water/ 2 | /// 贪心+相向双指针,每次贪心的移动height[left],height[right]的较小值 3 | fn container_with_most_water(height: Vec) -> i32 { 4 | let len = height.len(); 5 | let (mut left, mut right) = (0, len - 1); 6 | let mut max_area = 0; 7 | while left < right { 8 | if height[left] < height[right] { 9 | // 容器的高度等于左右挡板的最小值(木桶短板原理),宽度等于right-left 10 | max_area = max_area.max((right - left) as i32 * height[left]); 11 | left += 1; 12 | } else { 13 | max_area = max_area.max((right - left) as i32 * height[right]); 14 | right -= 1; 15 | } 16 | } 17 | max_area 18 | } 19 | 20 | #[test] 21 | fn test_container_with_most_water() { 22 | const TEST_CASES: [(&[i32], i32); 1] = [(&[1, 8, 6, 2, 5, 4, 8, 3, 7], 49)]; 23 | for (input, output) in TEST_CASES { 24 | assert_eq!(container_with_most_water(input.to_vec()), output); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/greedy/count_number_of_nice_subarrays.rs: -------------------------------------------------------------------------------- 1 | //! https://leetcode.com/problems/count-number-of-nice-subarrays/ 2 | 3 | /// O(n^2) time complex, not passed 4 | fn number_of_subarrays_prefix_sum_brute_force(mut nums: Vec, k: i32) -> i32 { 5 | let n = nums.len(); 6 | // nums数组延长一个长度,方便转为前缀和数组(nums[i]表示数组前i项里有几个奇数) 7 | nums.insert(0, 0); 8 | for i in 1..=n { 9 | nums[i] = nums[i - 1] + i32::from(nums[i] % 2 == 1); 10 | } 11 | let mut ret = 0; 12 | for i in 1..=n { 13 | // 因为k至少为1,因此j遍历到i的前一个位置即可 14 | for j in 0..i { 15 | // 判断子数组nums[j..=i]是否含有k个奇数 16 | if nums[i] - nums[j] == k { 17 | ret += 1; 18 | } 19 | } 20 | } 21 | ret 22 | } 23 | 24 | /// 有点贪心脑筋急转弯的思想,O(n)一次遍历 25 | fn number_of_subarrays(nums: Vec, k: i32) -> i32 { 26 | let n = nums.len(); 27 | // cnt有点像上面解法里的前缀和数组 28 | let mut cnt = vec![0_u16; n + 1]; 29 | // 拥有0个奇数的子数组有1个(就是空数组) 30 | cnt[0] = 1; 31 | // odd类似上面解法里的nums[i-1] 32 | let mut odd = 0_usize; 33 | let k = k as usize; 34 | 35 | let mut ret = 0; 36 | for num in nums { 37 | if num % 2 == 1 { 38 | odd += 1; 39 | } 40 | if odd >= k { 41 | ret += i32::from(cnt[odd - k]); 42 | } 43 | cnt[odd] += 1; 44 | } 45 | 46 | ret 47 | } 48 | -------------------------------------------------------------------------------- /src/greedy/di_string_match.rs: -------------------------------------------------------------------------------- 1 | /// https://leetcode.com/problems/di-string-match/ 2 | fn di_string_match(s: String) -> Vec { 3 | let n = s.len(); 4 | let mut ret = Vec::with_capacity(n + 1); 5 | let (mut lo, mut hi) = (0_i32, n as i32); 6 | for ch in s.into_bytes() { 7 | if ch == b'I' { 8 | ret.push(lo); 9 | lo += 1; 10 | } else { 11 | ret.push(hi); 12 | hi -= 1; 13 | } 14 | } 15 | ret.push(hi); // or ret.push(lo); 16 | ret 17 | } 18 | 19 | #[test] 20 | fn test_di_string_match() { 21 | const TEST_CASES: [(&str, &[i32]); 2] = [ 22 | // 输入一个仅有I和D组成的字符串,例如输入"IDID"长度为4的字符串,则准备0..=4总共5个数 23 | // 第一个I表示ret[1]>ret[0],第二个D表示ret[2] i32 { 3 | let mut factor_5_count = 0; 4 | while n != 0 { 5 | n /= 5; 6 | factor_5_count += n; 7 | } 8 | factor_5_count 9 | } 10 | -------------------------------------------------------------------------------- /src/greedy/gas_station.rs: -------------------------------------------------------------------------------- 1 | /// https://leetcode.com/problems/gas-station/ 2 | fn can_complete_circuit(gas: Vec, cost: Vec) -> i32 { 3 | let (mut start, mut oil_remain) = (0, 0); 4 | let (mut total_gas, mut total_cost) = (0, 0); 5 | for i in 0..gas.len() { 6 | total_gas += gas[i]; 7 | total_cost += cost[i]; 8 | oil_remain += gas[i] - cost[i]; 9 | /* 从起点start=a出发,到不了c站,说明从a到c的任意一个站b作为出发点,也到不了c,证明如下 10 | 假设a,b,c相连,则有关系式: gas[a]-cost[a] + gas[b]-cost[b] + gas[c]-cost[c] < 0 11 | 由于a可以作为出发点: gas[b]-cost[b] + gas[c]-cost[c] < 0 12 | 由于b可以作为出发点,所以gas[b]-cost[b]也是大于0,但是仍然不足以走到c 13 | 所以a出发到不了c这就排除掉从a到c所有点作为出发点的可能性,可以将出发点假设成c的后一个点 14 | */ 15 | if oil_remain < 0 { 16 | start = i as i32 + 1; 17 | oil_remain = 0; 18 | } 19 | } 20 | if total_gas < total_cost { 21 | return -1; 22 | } 23 | start 24 | } 25 | 26 | #[test] 27 | fn test_can_complete_circuit() { 28 | const TEST_CASES: [(&[i32], &[i32], i32); 2] = [ 29 | (&[1, 2, 3, 4, 5], &[3, 4, 5, 1, 2], 3), 30 | (&[2, 3, 4], &[3, 4, 3], -1), 31 | ]; 32 | 33 | for (gas, cost, start_index) in TEST_CASES { 34 | assert_eq!( 35 | can_complete_circuit(gas.to_vec(), cost.to_vec()), 36 | start_index 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/greedy/int_to_roman.rs: -------------------------------------------------------------------------------- 1 | /*! https://leetcode.com/problems/integer-to-roman/ 2 | & https://leetcode.com/problems/roman-to-integer/ 3 | 4 | # 贪心: 每次尽可能匹配数值更大的罗马字母 5 | 例如M是最大的罗马字母,如果num=9000,则将M重复9次 6 | ```python 7 | def in_to_roman(num: int) -> str: 8 | ret = '' 9 | for (integer, roman) in int_to_roman: 10 | if num >= integer: 11 | count, num = divmod(num, integer) 12 | ret += roman * count 13 | return ret 14 | ``` 15 | 16 | def roman_to_int(s: str) -> int: 17 | ret = 0 18 | # 用HashMap可能会快点 19 | for (integer, roman) in int_to_roman: 20 | n = len(roman) 21 | while s.startswith(roman): 22 | ret += integer 23 | s = s[n:] 24 | return ret 25 | */ 26 | const INT_TO_ROMAN: [(i32, &[u8]); 13] = [ 27 | (1000, b"M"), 28 | (900, b"CM"), 29 | (500, b"D"), 30 | (400, b"CD"), 31 | (100, b"C"), 32 | (90, b"XC"), 33 | (50, b"L"), 34 | (40, b"XL"), 35 | (10, b"X"), 36 | (9, b"IX"), 37 | (5, b"V"), 38 | (4, b"IV"), 39 | (1, b"I"), 40 | ]; 41 | 42 | fn int_to_roman(mut num: i32) -> String { 43 | let mut ret = Vec::new(); 44 | for &(int, roman) in &INT_TO_ROMAN { 45 | // while num >= int { 46 | // ret.extend_from_slice(roman); 47 | // num -= int; 48 | // } 49 | if num >= int { 50 | ret.extend(roman.repeat((num / int) as usize)); 51 | num %= int; 52 | } 53 | } 54 | unsafe { String::from_utf8_unchecked(ret) } 55 | } 56 | 57 | fn roman_to_int(s: String) -> i32 { 58 | let s = s.into_bytes(); 59 | let len = s.len(); 60 | let mut i = 0; 61 | let mut ret = 0; 62 | for &(int, roman) in &INT_TO_ROMAN { 63 | let roman_len = roman.len(); 64 | while s[i..].starts_with(roman) { 65 | ret += int; 66 | i += roman_len; 67 | if i >= len { 68 | return ret; 69 | } 70 | } 71 | } 72 | unreachable!() 73 | } 74 | 75 | #[test] 76 | fn test() { 77 | const TEST_CASES: [(&str, i32); 2] = [("LVIII", 58), ("MCMXCIV", 1994)]; 78 | for &(roman, int) in &TEST_CASES { 79 | assert_eq!(int_to_roman(int), roman); 80 | assert_eq!(roman_to_int(roman.to_string()), int); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/greedy/majority_element.rs: -------------------------------------------------------------------------------- 1 | /// https://leetcode.com/problems/majority-element/ 2 | /// 寻找数组中出现次数大于len/2次的元素 3 | fn majority_element(nums: Vec) -> i32 { 4 | let n = nums.len(); 5 | if n == 1 { 6 | return nums[0]; 7 | } 8 | let half = (n / 2) as u16; 9 | let mut cnt = std::collections::HashMap::with_capacity(n); 10 | for num in nums { 11 | if let Some(count) = cnt.get_mut(&num) { 12 | if half.eq(count) { 13 | return num; 14 | } 15 | *count += 1; 16 | } else { 17 | cnt.insert(num, 1_u16); 18 | } 19 | } 20 | unsafe { 21 | std::hint::unreachable_unchecked(); 22 | } 23 | } 24 | 25 | fn majority_element_best(nums: Vec) -> i32 { 26 | let mut count: u16 = 0; 27 | let mut candidate = 0; 28 | for num in nums { 29 | if count == 0 { 30 | candidate = num; 31 | } 32 | if num == candidate { 33 | count += 1; 34 | } else { 35 | count -= 1; 36 | } 37 | } 38 | candidate 39 | } 40 | -------------------------------------------------------------------------------- /src/greedy/maximum_subarray.rs: -------------------------------------------------------------------------------- 1 | /*! https://leetcode.com/problems/maximum-subarray/ 2 | 53. 最大子序和 3 | 给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和 4 | O(n)的算法:贪心: if cur_sum+nums[i] < nums[i],则以舍弃前面的元素,从i开始从新算最大长度 5 | O(n)的算法:动态规划(「滚动数组」): 如果当前元素的前一个元素大于0,则当前元素的值 += 前一个元素的值 6 | O(logn)的算法?分治?: 求区间内最值用「线段树」,但是加上预处理之后,就跟贪心一样是O(n),本题没有logn的解法 7 | 这个分治方法类似于「线段树求解 LCIS 问题」的 pushUp 操作 8 | */ 9 | 10 | fn maximum_subarray_dp(mut nums: Vec) -> i32 { 11 | let size = nums.len(); 12 | let mut max_sum = nums[0]; 13 | for i in 1..size { 14 | if nums[i - 1] > 0 { 15 | nums[i] += nums[i - 1]; 16 | } 17 | max_sum = max_sum.max(nums[i]); 18 | } 19 | max_sum 20 | } 21 | -------------------------------------------------------------------------------- /src/greedy/mod.rs: -------------------------------------------------------------------------------- 1 | /*! 贪心或脑筋急转弯的题目合集 2 | # 几个必须掌握的贪心算法题 3 | - [x] http://leetcode.com/problems/majority-number/ 4 | - [ ] http://lintcode.com/problem/create-maximum-number/ 5 | - [x] http://leetcode.com/problems/jump-game-ii/ (dp题) 6 | - [x] http://leetcode.com/problems/jump-game/ (dp题) 7 | - [x] http://leetcode.com/problems/gas-station/ 8 | - [ ] http://lintcode.com/problem/delete-digits/ 9 | - [ ] http://lintcode.com/problem/task-scheduler/ 10 | */ 11 | mod airplane_seat_assignment_probability; 12 | mod candy; 13 | mod container_with_most_water; 14 | mod count_number_of_nice_subarrays; 15 | mod di_string_match; 16 | mod factorial_trailing_zeroes; 17 | mod gas_station; 18 | mod int_to_roman; 19 | mod majority_element; 20 | mod maximum_subarray; 21 | mod partition_labels; 22 | mod prefix_sum_subarray; 23 | mod product_of_array_except_self; 24 | -------------------------------------------------------------------------------- /src/greedy/partition_labels.rs: -------------------------------------------------------------------------------- 1 | //! https://leetcode.com/problems/partition-labels/ 2 | fn partition_labels(s: String) -> Vec { 3 | const NOT_FOUND: usize = 501; 4 | let s = s.into_bytes(); 5 | // s.len in range [1,500] 6 | let mut last_occur = vec![0_usize; b'z' as usize + 1]; 7 | for (i, byte) in s.iter().enumerate() { 8 | last_occur[*byte as usize] = i; 9 | } 10 | let mut ret = Vec::new(); 11 | let mut cur_chunk_start = 0; 12 | // 已扫描的字符能去到的最远位置 13 | let mut cur_chunk_end = 0; 14 | for (i, byte) in s.iter().enumerate() { 15 | cur_chunk_end = cur_chunk_end.max(last_occur[*byte as usize]); 16 | // 当前char不能纳入cur_chunk,因为与前面的某个char重复了,切一刀后当前char作为新的chunk的起点 17 | if i == cur_chunk_end { 18 | ret.push((cur_chunk_end - cur_chunk_start + 1) as i32); 19 | cur_chunk_start = cur_chunk_end + 1; 20 | } 21 | } 22 | ret 23 | } 24 | 25 | #[test] 26 | fn test_partition_labels() { 27 | const TEST_CASES: [(&str, &[i32]); 1] = [("ababcbacadefegdehijhklij", &[9, 7, 8])]; 28 | for (s, expected) in TEST_CASES { 29 | assert_eq!(partition_labels(s.to_string()), expected.to_vec()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/greedy/product_of_array_except_self.rs: -------------------------------------------------------------------------------- 1 | /// https://leetcode.com/problems/product-of-array-except-self 2 | fn product_except_self(nums: Vec) -> Vec { 3 | // 右上三角,从下到上,从右到左累乘的结果 4 | let mut top_right = 1; 5 | // 左下三角,从上到下,从左到右累乘的结果 6 | let mut bottom_left = 1; 7 | 8 | let n = nums.len(); 9 | let mut ret = vec![1; n]; 10 | for i in (0..n).rev() { 11 | ret[i] *= top_right; 12 | top_right *= nums[i]; 13 | } 14 | for i in 0..n { 15 | ret[i] *= bottom_left; 16 | bottom_left *= nums[i]; 17 | } 18 | ret 19 | } 20 | 21 | /// https://leetcode.com/problems/gou-jian-cheng-ji-shu-zu-lcof 22 | /// 这题不看答案我真没想到累积「上下角」和累积「下三角」 23 | /// 注意这是超时解答 24 | #[allow(clippy::needless_range_loop)] 25 | fn construct_product_array(nums: &[i32]) -> Vec { 26 | let n = nums.len(); 27 | let mut ret = vec![1; n]; 28 | // 右上三角区域的乘积运算 29 | for i in 0..n { 30 | for j in i + 1..n { 31 | // dbg!((i, j, nums[j])); 32 | ret[i] *= nums[j]; 33 | } 34 | } 35 | 36 | // dbg!("bottom-left corner"); 37 | // 左下三角区域的乘积运算 38 | for i in 0..n { 39 | for j in 0..i { 40 | ret[i] *= nums[j]; 41 | } 42 | } 43 | ret 44 | } 45 | 46 | /** T表示一行中需要累乘的元素 47 | 1 2 3 4 48 | 1 T T T 49 | 2 T T T 50 | 3 T T T 51 | 4 T T T 52 | */ 53 | #[test] 54 | fn test_construct_product_array() { 55 | const TEST_CASES: [(&[i32], &[i32]); 3] = [ 56 | (&[1, 2, 3, 4], &[24, 12, 8, 6]), 57 | (&[1, 0, 3, 4], &[0, 12, 0, 0]), 58 | (&[1, 0, 3, 0], &[0, 0, 0, 0]), 59 | ]; 60 | 61 | for &(input, output) in &TEST_CASES { 62 | assert_eq!(construct_product_array(input), output.to_vec()); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(test, rustc_private, is_sorted, control_flow_enum)] 2 | // cargo clippy --tests -- -Wclippy::cargo -Wclippy::nursery -Wclippy::pedantic 3 | // clippy::restriction 4 | #![warn(clippy::nursery, clippy::cargo, clippy::pedantic)] 5 | #![allow( 6 | dead_code, 7 | // vec_vec: use of irregular braces for `vec!` macro 8 | clippy::nonstandard_macro_braces, 9 | clippy::ptr_arg, 10 | clippy::uninlined_format_args, 11 | /* clippy::pedantic */ 12 | clippy::borrow_as_ptr, 13 | clippy::cast_sign_loss, 14 | clippy::cast_possible_truncation, 15 | clippy::cast_possible_wrap, 16 | clippy::needless_pass_by_value, 17 | clippy::match_on_vec_items, 18 | clippy::non_ascii_literal, 19 | clippy::module_name_repetitions, 20 | clippy::doc_markdown, 21 | /* clippy::restriction */ 22 | // clippy::mod_module_files, 23 | // clippy::dbg_macro, 24 | // clippy::pub_use, 25 | // clippy::undocumented_unsafe_blocks, 26 | // clippy::separated_literal_suffix, // add in 1.58, TODO use regex match and replace to fix it? 27 | // clippy::unseparated_literal_suffix, 28 | // clippy::blanket_clippy_restriction_lints, 29 | // clippy::integer_division, 30 | // clippy::integer_arithmetic, 31 | // clippy::float_arithmetic, 32 | // clippy::modulo_arithmetic, 33 | // clippy::cast_sign_loss, 34 | // clippy::as_conversions, 35 | // clippy::default_numeric_fallback, 36 | // clippy::pattern_type_mismatch, 37 | // clippy::clone_on_ref_ptr, 38 | // clippy::indexing_slicing, 39 | // clippy::str_to_string, 40 | // clippy::unwrap_used, 41 | // clippy::unreachable, 42 | // clippy::implicit_return, 43 | // clippy::shadow_reuse, 44 | // clippy::shadow_same, 45 | // clippy::shadow_unrelated, 46 | // clippy::missing_docs_in_private_items, 47 | // clippy::else_if_without_else 48 | )] 49 | #![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))] 50 | #![doc(html_playground_url = "https://play.rust-lang.org/")] 51 | #[cfg(feature = "rustc_private")] 52 | extern crate rustc_graphviz; 53 | #[cfg(feature = "rustc_private")] 54 | extern crate rustc_lexer; 55 | #[cfg(feature = "rustc_private")] 56 | extern crate rustc_span; 57 | extern crate test; 58 | 59 | // Macros can only be used after they have been defined(macro_use) 60 | #[macro_use] 61 | mod macros; 62 | mod bfs_dfs_backtracking; 63 | mod binary_search; 64 | mod binary_tree; 65 | mod bitwise; 66 | mod code_snippets; 67 | mod compiler; 68 | mod counter; 69 | mod data_structure; 70 | mod dp; 71 | mod easy; 72 | mod graph; 73 | mod greedy; 74 | mod linked_list; 75 | mod math; 76 | mod random; 77 | mod two_sum_two_pointers; 78 | mod uncategorized; 79 | -------------------------------------------------------------------------------- /src/linked_list/insertion_sort_linked_list.rs: -------------------------------------------------------------------------------- 1 | //! https://leetcode.com/problems/insertion-sort-list/ 2 | use super::ListNode; 3 | 4 | fn insertion_sort_list(head: Option>) -> Option> { 5 | if head.is_none() { 6 | return head; 7 | } 8 | let mut dummy = Some(Box::new(ListNode { val: 0, next: head })); 9 | let mut last_sorted = &mut dummy.as_mut()?.next as *mut Option>; 10 | unsafe { 11 | // TODO 想一边遍历一边修改就不能while let Some()遍历吗? 12 | while (*last_sorted).as_ref()?.next.is_some() { 13 | if (*last_sorted).as_ref()?.val <= (*last_sorted).as_ref()?.next.as_ref()?.val { 14 | last_sorted = &mut (*last_sorted).as_mut()?.next as *mut _; 15 | } else { 16 | let mut unsorted = (*last_sorted).as_mut()?.next.take(); 17 | (*last_sorted).as_mut()?.next = unsorted.as_mut()?.next.take(); 18 | let mut ptr = &mut dummy as *mut Option>; 19 | while (*ptr).as_ref()?.next.as_ref()?.val <= unsorted.as_ref()?.val { 20 | ptr = &mut (*ptr).as_mut()?.next as *mut _; 21 | } 22 | // 将新节点插入到ptr的后一个位置 23 | unsorted.as_mut()?.next = (*ptr).as_mut()?.next.take(); 24 | (*ptr).as_mut()?.next = unsorted; 25 | } 26 | } 27 | } 28 | dummy?.next 29 | } 30 | 31 | fn insertion_sort_list_dirty_solution(head: Option>) -> Option> { 32 | let mut nums: Vec = Vec::new(); 33 | let mut curr = head; 34 | while let Some(curr_node) = curr { 35 | nums.push(curr_node.val); 36 | curr = curr_node.next; 37 | } 38 | nums.sort_unstable(); 39 | let mut head = None; 40 | let mut curr = &mut head; 41 | for num in nums { 42 | *curr = Some(Box::new(ListNode::new(num))); 43 | curr = &mut curr.as_mut()?.next; 44 | } 45 | head 46 | } 47 | -------------------------------------------------------------------------------- /src/linked_list/linked_list_is_palindrome.rs: -------------------------------------------------------------------------------- 1 | use super::ListNode; 2 | 3 | fn is_palindrome(head: Option>) -> bool { 4 | let mut nums = Vec::new(); 5 | let mut curr = &head; 6 | while let Some(curr_inner) = curr { 7 | nums.push(curr_inner.val); 8 | curr = &curr_inner.next; 9 | } 10 | let nums_before_reverse = nums.clone(); 11 | nums.reverse(); 12 | nums_before_reverse == nums 13 | } 14 | 15 | #[cfg(test)] 16 | const TEST_CASES: [(&[i32], bool); 1] = [(&[0, 0], true)]; 17 | 18 | #[test] 19 | fn test_linked_list_is_palindrome() { 20 | for (ln, expected) in TEST_CASES { 21 | let head = super::arr_to_linked_list(ln); 22 | assert_eq!(is_palindrome(head), expected); 23 | } 24 | } 25 | 26 | // 仅仅适用于node.val在0~9之间 27 | #[cfg(FALSE)] 28 | fn is_palindrome_only_one_digit(head: Option>) -> bool { 29 | let mut asc = 0; 30 | let mut desc = 0; 31 | let mut desc_base = 1; 32 | 33 | let mut curr = head.as_ref(); 34 | while let Some(curr_node) = curr { 35 | asc = 10 * asc + curr_node.val; 36 | desc += curr_node.val * desc_base; 37 | desc_base *= 10; 38 | curr = curr_node.next.as_ref(); 39 | } 40 | asc == desc 41 | } 42 | -------------------------------------------------------------------------------- /src/linked_list/merge_two_sorted_linked_list.rs: -------------------------------------------------------------------------------- 1 | use super::ListNode; 2 | 3 | /// https://leetcode.com/problems/merge-two-sorted-lists/ 4 | fn merge_two_sorted_lists( 5 | mut l1: Option>, 6 | mut l2: Option>, 7 | ) -> Option> { 8 | let mut head = None; 9 | let mut curr = &mut head; 10 | while let (Some(n1), Some(n2)) = (&mut l1, &mut l2) { 11 | if n1.val < n2.val { 12 | let next = n1.next.take(); 13 | *curr = l1.take(); 14 | l1 = next; 15 | } else { 16 | let next = n2.next.take(); 17 | *curr = l2.take(); 18 | l2 = next; 19 | } 20 | curr = &mut curr.as_mut()?.next; 21 | } 22 | *curr = l1.or(l2); 23 | head 24 | } 25 | 26 | /** https://leetcode.com/problems/sort-list/ 27 | Rust暂且就先放O(logn)空间复杂度自上而下的递归归并排序把,即便上unsafe也很难挪动链表节点 28 | 用unsafe的原因,需要多个可变借用(head和slow)或链表某个节点的所有权要被slow和mid同时拥有,还需要take()切断 29 | 30 | 用unsafe的根本原因,快慢双指针遍历链表时,慢指针遍历到链表中点后要切断链表,所以慢指针要用可变借用 31 | 但是Rust所有权限制当链表存在一个可变借用时,就不能有其它的可变或不可变借用,所以快指针将无法定义。 32 | 此时的慢指针是个可变引用就类似RwLock或RWMutex的write_lock,当拥有一个WLock时不能拥有其他RLock/WLock 33 | 用unsafe将慢指针定义成原始指针,绕开Rust所有权机制的限制 34 | 35 | ```text 36 | let mut slow = head.as_mut(); 37 | ---- mutable borrow occurs here 38 | let mut fast = head.as_ref()?.next.as_ref(); 39 | ^^^^ immutable borrow occurs here 40 | ``` 41 | */ 42 | fn sort_linked_list_top_down_recursive(mut head: Option>) -> Option> { 43 | if head.as_ref()?.next.is_none() { 44 | return head; 45 | } 46 | let mut slow = &mut head as *mut Option>; 47 | // 因为right_part取的是慢指针的next,所以快指针初始值要比慢指针多一步 48 | let mut fast = head.as_ref()?.next.as_ref(); 49 | let mid = unsafe { 50 | while fast.is_some() && fast.as_ref()?.next.is_some() { 51 | slow = &mut (*slow).as_mut()?.next as *mut _; 52 | fast = fast?.next.as_ref()?.next.as_ref(); 53 | } 54 | let mid = (*slow).as_mut()?.next.take(); 55 | // cut left_part_list and right_part_list 56 | (*slow).as_mut()?.next = None; 57 | mid 58 | }; 59 | let left_part = sort_linked_list_top_down_recursive(head); 60 | let right_part = sort_linked_list_top_down_recursive(mid); 61 | merge_two_sorted_lists(left_part, right_part) 62 | } 63 | 64 | fn merge_two_lists_match_solution( 65 | mut l1: Option>, 66 | mut l2: Option>, 67 | ) -> Option> { 68 | let mut head = None; 69 | let mut curr = &mut head; 70 | loop { 71 | // 由于add_two_numbers一题的4个模式识别分支要么l1,l2全改,要么全不改,所以不需要&mut l1 72 | // 由于第一个分支改l1或l2,二选一的话就l1和l2都需要&mut了 73 | let val; 74 | match (&mut l1, &mut l2) { 75 | (Some(n1), Some(n2)) => { 76 | if n1.val < n2.val { 77 | val = n1.val; 78 | l1 = n1.next.take(); 79 | } else { 80 | val = n2.val; 81 | l2 = n2.next.take(); 82 | }; 83 | *curr = Some(Box::new(ListNode::new(val))); 84 | curr = &mut curr.as_mut()?.next; 85 | } 86 | (Some(n1), None) => { 87 | *curr = Some(n1.clone()); 88 | break; 89 | } 90 | (None, Some(n2)) => { 91 | *curr = Some(n2.clone()); 92 | break; 93 | } 94 | (None, None) => { 95 | break; 96 | } 97 | } 98 | } 99 | head 100 | } 101 | -------------------------------------------------------------------------------- /src/linked_list/middle_of_linked_list.rs: -------------------------------------------------------------------------------- 1 | //! https://leetcode.com/problems/middle-of-the-linked-list/ 2 | use super::ListNode; 3 | 4 | /// 链表题型的快慢双指针算法 5 | /// 快指针每次两步,慢指针每次一步 6 | /// 快指针到尾的时候,慢指针就是中点(左中位节点) 7 | fn middle_of_linked_list(head: Option>) -> Option> { 8 | let mut slow = head.as_ref(); 9 | let mut fast = head.as_ref(); 10 | while fast.is_some() && fast?.next.is_some() { 11 | slow = slow?.next.as_ref(); 12 | fast = fast?.next.as_ref()?.next.as_ref(); 13 | } 14 | slow.cloned() 15 | } 16 | 17 | #[test] 18 | fn test_middle_of_linked_list() { 19 | use super::{arr_to_linked_list, linked_list_to_vec}; 20 | const TEST_CASES: [(&[i32], &[i32]); 2] = [ 21 | (&[1, 2, 3, 4, 5], &[3, 4, 5]), 22 | (&[1, 2, 3, 4, 5, 6], &[4, 5, 6]), 23 | ]; 24 | for (input, output) in TEST_CASES { 25 | let input = arr_to_linked_list(input); 26 | assert_eq!(linked_list_to_vec(&middle_of_linked_list(input)), output); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/linked_list/mod.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | # 链表 3 | 4 | ## 侵入式和非侵入式 5 | 6 | Rust标准库的双向链表和C++ STL的链表一样都是较为简单的非侵入式链表(non-intrusive),侵入式链表好处的内存利用率高,缺点是指针生命周期管理太复杂 7 | 8 | ## 非随机访问数据结构 9 | 10 | array is a 'random access data structure' 11 | 12 | 数组可以通过下标乘元素长度计算出内存偏移地址去任意下标的访问数据,但是链表访问第N个节点的数据前必须遍历前N-1个元素 13 | 14 | 所以数组是随机访问数据结构,而链表是非随机访问数据结构 15 | 16 | ## Rust链表可以用dummyHead去遍历也可以不用 17 | 18 | ## 泛型的支持 19 | 20 | ```test 21 | struct Node { 22 | data: T, 23 | next: Option> 24 | } 25 | 26 | /// impl $struct 或 impl $trait for $struct : impl 的尖括号内要写什么 27 | /// $struct 的泛型参数, $trait 的生命周期参数, $struct 的生命周期参数 28 | impl Node { 29 | 30 | } 31 | ``` 32 | */ 33 | 34 | mod add_two_linked_list; 35 | mod insertion_sort_linked_list; 36 | mod is_circular_loop; 37 | mod linked_list_is_palindrome; 38 | mod merge_two_sorted_linked_list; 39 | mod middle_of_linked_list; 40 | mod nth_node_from_end; 41 | mod partition_list; 42 | mod plus_one; 43 | mod reverse_linked_list; 44 | mod reverse_linked_list_2; 45 | mod swap_nodes_in_pairs; 46 | 47 | /// non-interest single_linked_list Node 48 | #[derive(PartialEq, Eq, Clone, Debug)] 49 | struct ListNode { 50 | val: i32, 51 | next: Option>, 52 | } 53 | 54 | impl ListNode { 55 | #[inline] 56 | const fn new(val: i32) -> Self { 57 | Self { next: None, val } 58 | } 59 | } 60 | 61 | impl std::fmt::Display for ListNode { 62 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 63 | write!(f, "{:?}", linked_list_to_vec(&Some(Box::new(self.clone())))) 64 | } 65 | } 66 | 67 | // Option的设计优点之一: 链表题可以不使用dummy_head也能生成链表后返回头部 68 | fn arr_to_linked_list(nums: &[i32]) -> Option> { 69 | let mut head = None; 70 | let mut curr = &mut head; 71 | for num in nums { 72 | *curr = Some(Box::new(ListNode::new(*num))); 73 | curr = &mut curr.as_mut()?.next; 74 | } 75 | head 76 | } 77 | 78 | /** 79 | ```java 80 | ListNode dummy = new ListNode(); 81 | ListNode cur = dummyHead; 82 | for (int num: nums) { 83 | cur.next = new ListNode(num); 84 | cur = cur.next; 85 | } 86 | return dummy.next; 87 | ``` 88 | */ 89 | #[cfg(FALSE)] 90 | fn arr_to_linked_list_with_dummy(nums: &[i32]) -> Option> { 91 | let mut dummy = Some(Box::new(ListNode::new(0))); 92 | let mut curr = &mut dummy; 93 | for num in nums { 94 | let curr_node = curr.as_mut()?; 95 | curr_node.next = Some(Box::new(ListNode::new(*num))); 96 | curr = &mut curr_node.next; 97 | } 98 | dummy?.next 99 | } 100 | 101 | /** 102 | ```java 103 | static int[] listNodeToArray(ListNode head) { 104 | List nums = new ArrayList<>(); 105 | ListNode curr = head; 106 | while (curr != null) { 107 | nums.add(curr.val); 108 | curr = curr.next; 109 | } 110 | return nums.stream().mapToInt(i -> i).toArray(); 111 | } 112 | ``` 113 | */ 114 | fn linked_list_to_vec(head: &Option>) -> Vec { 115 | let mut nums: Vec = Vec::new(); 116 | // 由于链表转数组只需要读链表不需要修改链表各节点,所以curr=head即可而不是curr=&mut head,而且代码也简洁多了 117 | // 但是一旦用了dummyHead,可能只需要修改head一个节点,但是出于遍历原因可变引用需要往后传染,所以后面的节点被迫也用可变引用 118 | let mut curr = head; 119 | while let Some(curr_node) = curr { 120 | nums.push(curr_node.val); 121 | curr = &curr_node.next; 122 | } 123 | nums 124 | } 125 | 126 | #[test] 127 | fn test_arr_to_linked_list() { 128 | let head = arr_to_linked_list(&[1, 2, 3, 4, 5]); 129 | let nums_vec = linked_list_to_vec(&head); 130 | assert_eq!(nums_vec, vec![1, 2, 3, 4, 5]); 131 | } 132 | -------------------------------------------------------------------------------- /src/linked_list/nth_node_from_end.rs: -------------------------------------------------------------------------------- 1 | use super::ListNode; 2 | 3 | /// https://leetcode.com/problems/kth-node-from-end-of-list-lcci/ 4 | /// 找出单向链表中倒数第 k 个节点,返回该节点的值,容易想到用滑动窗口的方法,宽度为k窗口往右划底时,左边界就是倒数第k个节点 5 | fn kth_to_last(head: Option>, k: i32) -> i32 { 6 | fn helper(head: Option>, k: i32) -> Option { 7 | let dummy = Some(Box::new(ListNode { val: 0, next: head })); 8 | let mut left = dummy.as_ref(); 9 | let mut right = dummy.as_ref(); 10 | // 扩宽右窗口,使得窗口宽度等于k 11 | for _ in 0..k { 12 | right = right?.next.as_ref(); 13 | } 14 | while right?.next.is_some() { 15 | right = right?.next.as_ref(); 16 | left = left?.next.as_ref(); 17 | } 18 | Some(left?.next.as_ref()?.val) 19 | } 20 | helper(head, k).unwrap_or_default() 21 | } 22 | 23 | #[test] 24 | fn test_kth_to_last() { 25 | const TEST_CASES: [(&[i32], i32, i32); 2] = [(&[1, 2, 3, 4, 5], 2, 4), (&[1], 1, 1)]; 26 | for (nums, k, output) in TEST_CASES { 27 | assert_eq!(kth_to_last(super::arr_to_linked_list(nums), k), output); 28 | } 29 | } 30 | 31 | /** https://leetcode.com/problems/remove-nth-node-from-end-of-list/ 32 | ## Rust挪动链表节点的要点 33 | 1. 双指针(快慢双指针 or 滑动窗口) 34 | 如果类似获取链表中点值这样的只读操作,用两个不可变引用没问题 35 | 但是一旦像这题左指针需要可变,右指针不可变,就会违反所有权机制——只能存在一个可变或多个不可变,类似RwLock或Go的RwMutex 36 | 当写锁存在时不能有其它读锁,需要unsafe绕开所有权检查 37 | 2. 使用?不方便找到哪一行因None而unwrap 38 | 我的经验是首先全用unwrap,等代码调试对了后再改成? 39 | */ 40 | fn remove_nth_from_end(head: Option>, n: i32) -> Option> { 41 | let mut dummy = Some(Box::new(ListNode { val: 0, next: head })); 42 | let mut left = &mut dummy as *mut Option>; 43 | let mut right = dummy.as_ref(); 44 | for _ in 0..n { 45 | right = right?.next.as_ref(); 46 | } 47 | while right?.next.is_some() { 48 | right = right?.next.as_ref(); 49 | left = unsafe { &mut (*left).as_mut()?.next } as *mut _; 50 | } 51 | unsafe { 52 | // or: (*left).as_mut()?.next = (*left).as_mut()?.next.take()?.next; 53 | (*left).as_mut()?.next = (*left).as_mut()?.next.as_mut()?.next.take(); 54 | } 55 | dummy?.next 56 | } 57 | 58 | #[test] 59 | fn test_remove_nth_from_end() { 60 | use super::arr_to_linked_list; 61 | const TEST_CASES: [(&[i32], i32, &[i32]); 2] = 62 | [(&[1, 2, 3, 4, 5], 2, &[1, 2, 3, 5]), (&[1], 1, &[])]; 63 | for (nums, k, output) in TEST_CASES { 64 | let (nums, output) = (arr_to_linked_list(nums), arr_to_linked_list(output)); 65 | assert_eq!(remove_nth_from_end(nums, k), output); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/linked_list/partition_list.rs: -------------------------------------------------------------------------------- 1 | use super::ListNode; 2 | 3 | /// https://leetcode.com/problems/partition-list/ 4 | fn partition_list(mut head: Option>, x: i32) -> Option> { 5 | // linked_list less than x 6 | let mut less_than_x_head = None; 7 | let mut less_than_x = &mut less_than_x_head; 8 | // linked_list greater or equal than x 9 | let mut greater_than_x_head = None; 10 | let mut greater_than_x = &mut greater_than_x_head; 11 | while let Some(mut box_node) = head { 12 | head = box_node.next.take(); 13 | if box_node.val < x { 14 | *less_than_x = Some(box_node); 15 | less_than_x = &mut less_than_x.as_mut()?.next; 16 | } else { 17 | *greater_than_x = Some(box_node); 18 | greater_than_x = &mut greater_than_x.as_mut()?.next; 19 | } 20 | } 21 | *less_than_x = greater_than_x_head; 22 | less_than_x_head 23 | } 24 | 25 | #[test] 26 | fn test_partition_list() { 27 | use super::arr_to_linked_list; 28 | const TEST_CASES: [(&[i32], i32, &[i32]); 1] = [(&[1, 4, 3, 2, 5, 2], 3, &[1, 2, 2, 4, 3, 5])]; 29 | for (head, x, output) in TEST_CASES { 30 | let (head, output) = (arr_to_linked_list(head), arr_to_linked_list(output)); 31 | assert_eq!(partition_list(head, x), output); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/linked_list/plus_one.rs: -------------------------------------------------------------------------------- 1 | use super::ListNode; 2 | 3 | fn bottom_up_get_carry(node: &mut Option>) -> i32 { 4 | match node { 5 | Some(ref mut node) => { 6 | let add_result = node.val + bottom_up_get_carry(&mut node.next); 7 | node.val = add_result % 10; 8 | add_result / 10 9 | } 10 | // 自底向上,这是链表的尾部 11 | None => 1, 12 | } 13 | } 14 | 15 | /// https://leetcode.com/problems/plus-one-linked-list/submissions/ 16 | fn plus_one(mut head: Option>) -> Option> { 17 | let carry = bottom_up_get_carry(&mut head); 18 | if carry == 1 { 19 | Some(Box::new(ListNode { val: 1, next: head })) 20 | } else { 21 | head 22 | } 23 | } 24 | 25 | #[test] 26 | fn test_plus_one() { 27 | use super::{arr_to_linked_list, linked_list_to_vec}; 28 | const TEST_CASES: [(&[i32], &[i32]); 2] = [(&[1, 2, 3], &[1, 2, 4]), (&[9, 9], &[1, 0, 0])]; 29 | for &(input, output) in &TEST_CASES { 30 | assert_eq!( 31 | linked_list_to_vec(&plus_one(arr_to_linked_list(input))), 32 | output 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/linked_list/reverse_linked_list.rs: -------------------------------------------------------------------------------- 1 | use super::ListNode; 2 | 3 | #[allow(clippy::missing_const_for_fn)] 4 | fn reverse_list(head: Option>) -> Option> { 5 | let mut has_rev = None; 6 | let mut not_rev = head; 7 | while let Some(mut not_rev_node) = not_rev { 8 | /* 9 | H: has_rev 10 | N: not_rev 11 | M: mut not_rev_node 12 | Before: 13 | None 1->2->3 14 | ^ ^ 15 | H N/M 16 | After: 17 | None 1->2->3 18 | ^ ^ ^ 19 | H M N 20 | */ 21 | not_rev = not_rev_node.next; 22 | /* 23 | Before: 24 | None 1->2->3 25 | ^ ^ ^ 26 | H M N 27 | After: 28 | None<-1 2->3 29 | ^ ^ ^ 30 | H M N 31 | */ 32 | not_rev_node.next = has_rev; 33 | /* 34 | Before: 35 | None<-1 2->3 36 | ^ ^ ^ 37 | H M N 38 | After: 39 | None<-1 2->3 40 | ^ ^ 41 | H N 42 | */ 43 | has_rev = Some(not_rev_node); 44 | } 45 | has_rev 46 | } 47 | 48 | #[test] 49 | fn test_traverse_two_list_node() { 50 | use crate::linked_list::{arr_to_linked_list, linked_list_to_vec}; 51 | const TEST_CASES: [(&[i32], &[i32]); 1] = [(&[1, 2, 3], &[3, 2, 1])]; 52 | for (input, output) in TEST_CASES { 53 | let head = arr_to_linked_list(input); 54 | assert_eq!(linked_list_to_vec(&reverse_list(head)), output.to_vec()); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/linked_list/reverse_linked_list_2.rs: -------------------------------------------------------------------------------- 1 | use super::ListNode; 2 | 3 | #[test] 4 | #[should_panic] 5 | fn test_reverse_range_inplace() { 6 | const TEST_CASES: [(&[i32], i32, i32, &[i32]); 3] = [ 7 | (&[1, 2, 3, 4, 5], 1, 5, &[5, 4, 3, 2, 1]), 8 | (&[1, 2, 3, 4], 1, 4, &[4, 3, 2, 1]), 9 | (&[1, 2, 3, 4, 5], 2, 4, &[1, 4, 3, 2, 5]), 10 | ]; 11 | 12 | for (input, m, n, output) in TEST_CASES { 13 | let head = super::arr_to_linked_list(input); 14 | let output_head = unsafe { reverse_range_inplace(head, m, n) }; 15 | assert_eq!(super::linked_list_to_vec(&output_head), output.to_vec()); 16 | } 17 | } 18 | 19 | #[allow(clippy::redundant_else)] 20 | unsafe fn reverse_range_inplace( 21 | head: Option>, 22 | m: i32, 23 | n: i32, 24 | ) -> Option> { 25 | let mut dummy = Some(Box::new(ListNode { val: 0, next: head })); 26 | let mut node_m_prev = &mut dummy as *mut Option>; 27 | for _ in 0..m - 1 { 28 | node_m_prev = &mut (*node_m_prev).as_mut()?.next as *mut Option>; 29 | } 30 | let mut rev_head = &mut (*node_m_prev).as_mut()?.next as *mut Option>; 31 | for _ in m..n { 32 | dbg!(line!()); 33 | dbg!(&(*rev_head)); 34 | if (*rev_head).is_none() { 35 | break; 36 | } 37 | let mut rev_head_next = (*rev_head).as_mut()?.next.take(); 38 | dbg!(line!()); 39 | if rev_head_next.is_none() { 40 | dbg!(line!()); 41 | // 相比C++/Python相同解法的代码,Rust要多写的部分 42 | (*rev_head).as_mut()?.next = (*node_m_prev).as_mut()?.next.take(); 43 | (*node_m_prev).as_mut()?.next = (*rev_head).take(); 44 | break; 45 | } else { 46 | dbg!(line!()); 47 | (*rev_head).as_mut()?.next = rev_head_next.as_mut()?.next.take(); 48 | } 49 | dbg!(line!()); 50 | rev_head_next.as_mut()?.next = (*node_m_prev).as_mut()?.next.take(); 51 | dbg!(line!()); 52 | (*node_m_prev).as_mut()?.next = rev_head_next; 53 | dbg!(line!()); 54 | // 相比C++/Python相同解法的代码,Rust要多写的部分 55 | dbg!(&(*rev_head)); 56 | if (*rev_head).as_mut()?.next.is_none() { 57 | let temp = (*node_m_prev).as_mut()?.val; 58 | (*node_m_prev).as_mut()?.val = (*rev_head).as_mut()?.val; 59 | dbg!(line!()); 60 | (*rev_head).as_mut()?.next = (*node_m_prev).as_mut()?.next.take(); 61 | dbg!(line!()); 62 | (*rev_head).as_mut()?.val = temp; 63 | dbg!(line!()); 64 | (*node_m_prev).as_mut()?.next = (*rev_head).take(); 65 | dbg!(line!()); 66 | break; 67 | } 68 | rev_head = &mut (*rev_head).as_mut()?.next as *mut _; 69 | dbg!(&(*rev_head)); 70 | } 71 | dbg!(&dummy); 72 | dummy?.next 73 | } 74 | -------------------------------------------------------------------------------- /src/linked_list/swap_nodes_in_pairs.rs: -------------------------------------------------------------------------------- 1 | use super::ListNode; 2 | 3 | struct Solution; 4 | 5 | /* Python版方便理解记忆 6 | def swap_pairs(self, head): 7 | if head and head.next: 8 | right = head.next 9 | head.next = self.swap_pairs(right.next) 10 | right.next = head 11 | return right 12 | # 5->None这种情况就不换 13 | return head 14 | */ 15 | impl Solution { 16 | fn swap_pairs_best(head: Option>) -> Option> { 17 | // TODO 用map unwrap Option,学到了,如果是head是None则不会走map的函数 18 | head.map(|mut left| match left.next.take() { 19 | None => left, 20 | Some(mut right) => { 21 | left.next = Self::swap_pairs(right.next.take()); 22 | right.next = Some(left); 23 | right 24 | } 25 | }) 26 | } 27 | 28 | fn swap_pairs(head: Option>) -> Option> { 29 | Self::recursive(head) 30 | } 31 | 32 | fn recursive(mut node: Option>) -> Option> { 33 | if node.is_some() && node.as_ref()?.next.is_some() { 34 | // right是一对节点中靠右节点(奇数下标的节点),node是靠左的 35 | let mut right = node.as_mut()?.next.take(); 36 | // 下一对的第一个 37 | let next_pair = right.as_mut()?.next.take(); 38 | node.as_mut()?.next = Self::recursive(next_pair); 39 | 40 | right.as_mut()?.next = node; 41 | right 42 | } else { 43 | node 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/macros.rs: -------------------------------------------------------------------------------- 1 | /// https://docs.rs/rustgym-util/0.2.4/src/rustgym_util/data.rs.html#2-6 2 | #[cfg(test)] 3 | macro_rules! vec_vec { 4 | [$($token_tree:tt),* $(,)?] => { 5 | vec![$(vec!$token_tree),*] 6 | }; 7 | } 8 | 9 | #[cfg(test)] 10 | macro_rules! vec_string { 11 | [$($str:expr),* $(,)?] => { 12 | vec![$(String::from($str)),*] 13 | }; 14 | } 15 | 16 | #[test] 17 | fn test_vec_i32() { 18 | let expected = vec![vec![17, 2], vec![-31], vec![3], vec![]]; 19 | assert_eq!(vec_vec![[17, 2], [-31], [3], []], expected); 20 | assert_eq!(vec_vec!([17, 2], [-31], [3], [],), expected); 21 | assert_eq!(vec_vec! {[17,2],[-31],[3],[],}, expected); 22 | } 23 | 24 | #[test] 25 | fn test_vec_string() { 26 | let strs = vec_string!["a", "b", "c"]; 27 | assert_eq!(strs[0], String::from("a")); 28 | } 29 | -------------------------------------------------------------------------------- /src/math/excel_sheet_column_title.rs: -------------------------------------------------------------------------------- 1 | /*! https://leetcode.com/problems/excel-sheet-column-title/ 2 | 26进制,例如excel的第26列,要输出成AB 3 | 要注意26进制是从[1,26]而不是[0,25] 4 | 5 | ## Some Math 6 | remainder∈[0,25] 7 | (1) num=quotient*26+remainder 8 | (2) remainder=num-quotient*26 9 | 10 | but we wanted_remainder∈[1,26] 11 | (3) num=quotient_2*26+wanted_remainder 12 | (4) (num-1)=quotient_2*26+(wanted_remainder-1) 13 | (5) wanted_remainder-1=(num-1)%26 14 | (6) wanted_remainder=(num-1)%26+1 15 | So quotient_2 = (num-wanted_remainder)/26 16 | */ 17 | fn excel_sheet_column_title(mut column_number: i32) -> String { 18 | const POSITION_NATION: i32 = 26; 19 | let mut s = vec![]; 20 | while column_number != 0 { 21 | // 例如26要显示成Z而非`10` 22 | let rem = (column_number - 1) % POSITION_NATION + 1; 23 | s.push(b'A' + rem as u8 - 1); 24 | column_number = (column_number - rem) / POSITION_NATION; 25 | } 26 | s.reverse(); 27 | unsafe { String::from_utf8_unchecked(s) } 28 | } 29 | 30 | /// https://leetcode.com/problems/excel-sheet-column-number/ 31 | fn excel_sheet_column_number(column_title: String) -> i32 { 32 | let mut num = 0; 33 | for each in column_title.into_bytes() { 34 | let rem = each - b'A' + 1; 35 | num = num * 26 + i32::from(rem); 36 | } 37 | num 38 | } 39 | 40 | #[test] 41 | fn test_excel_sheet_column_title() { 42 | const TEST_CASES: [(i32, &str); 3] = [(701, "ZY"), (1, "A"), (28, "AB")]; 43 | for (column_number, column_title) in TEST_CASES { 44 | assert_eq!(excel_sheet_column_title(column_number), column_title); 45 | assert_eq!( 46 | excel_sheet_column_number(column_title.to_string()), 47 | column_number 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/math/mod.rs: -------------------------------------------------------------------------------- 1 | //! 数学相关问题或位运算 2 | mod count_primes; 3 | mod excel_sheet_column_title; 4 | mod perfect_number; 5 | mod pow; 6 | mod sqrt; 7 | 8 | /// https://leetcode.com/problems/ugly-number/ 9 | const fn is_ugly(mut num: i32) -> bool { 10 | if num == 0 { 11 | return false; 12 | } 13 | while num % 2 == 0 { 14 | num /= 2; 15 | } 16 | while num % 3 == 0 { 17 | num /= 3; 18 | } 19 | while num % 5 == 0 { 20 | num /= 5; 21 | } 22 | num == 1 23 | } 24 | -------------------------------------------------------------------------------- /src/math/perfect_number.rs: -------------------------------------------------------------------------------- 1 | //! https://leetcode.com/problems/perfect-number/ 2 | //! perfect_number: 因数之和等于自身 3 | 4 | /// 在枚举时,我们只需要从 1 到 sqrt(n) 进行枚举即可。 5 | /// 这是因为如果 n 有一个大于 sqrt(n) 的因数 x,那么它一定有一个小于 sqrt(n) 的因数 n/x 6 | /// 所以求一个较小因数的同时也记入较大因数即可,一对一对地数更快 7 | #[allow(clippy::cast_precision_loss)] 8 | fn perfect_number(num: i32) -> bool { 9 | // num == 6 || num == 28 || num == 496 || num == 8128 || num == 33550336 10 | if num == 1 { 11 | return false; 12 | } 13 | let mut factors_sum = 1; 14 | for lower_factor in 2..=((num as f32).sqrt() as i32) { 15 | if num % lower_factor == 0 { 16 | factors_sum += lower_factor + (num / lower_factor); 17 | } 18 | } 19 | factors_sum == num 20 | } 21 | -------------------------------------------------------------------------------- /src/math/pow.rs: -------------------------------------------------------------------------------- 1 | /// https://leetcode.com/problems/powx-n/ 2 | fn my_pow(base: f64, mut exponent: i32) -> f64 { 3 | if exponent == i32::MIN { 4 | if base > 1.0 { 5 | return 0.0; 6 | } 7 | return 1.0; 8 | } 9 | let exponent_is_negative = if exponent < 0 { 10 | exponent = -exponent; 11 | true 12 | } else { 13 | false 14 | }; 15 | 16 | let mut ret = 1.0; 17 | let mut mask = base; 18 | while exponent != 0 { 19 | // 3^5=3^(2^2+2^0)=(3^2)^2+3,所以只有3和3^(2*2)对结果有贡献 20 | if exponent % 2 == 1 { 21 | ret *= mask; 22 | } 23 | mask *= mask; 24 | exponent /= 2; 25 | } 26 | if exponent_is_negative { 27 | ret = 1.0 / ret; 28 | } 29 | ret 30 | } 31 | 32 | #[test] 33 | fn test_my_pow() { 34 | #[allow(clippy::decimal_literal_representation)] 35 | const TEST_CASES: [(f64, i32, f64); 3] = [ 36 | (2.10, 3, 9.261), 37 | (2.0, -2, 0.25), 38 | (2.0, -2_147_483_648, 0.0), 39 | ]; 40 | for (x, n, pow_output) in TEST_CASES { 41 | assert!((my_pow(x, n) - pow_output).abs() < 10e-6); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/math/sqrt.rs: -------------------------------------------------------------------------------- 1 | /** https://leetcode.com/problems/sqrtx/ 2 | ## 牛顿连续均值求根法 3 | ```python 4 | def sqrt_newton_iterative(num: int) -> int: 5 | if num == 0: 6 | return 0 7 | # 牛顿法的初始值是num/2 8 | last_n, n = num, num / 2 9 | # 由于牛顿迭代法,next_n一定会比当前的n小,所以不需要加abs 10 | while last_n - n > 1e-3: 11 | last_n = n 12 | n = (last_n + num / last_n) / 2 13 | return int(n) 14 | ``` 15 | */ 16 | fn my_sqrt(num: i32) -> i32 { 17 | if num == 0 { 18 | return 0; 19 | } 20 | // 牛顿法迭代序列的第一项是x,第二项(也就初始值)是x/2,要用f64确保不会因为精度引起误差 21 | let num = f64::from(num); 22 | let (mut last_n, mut n) = (num, num / 2.0); 23 | while last_n - n > 1e-3 { 24 | last_n = n; 25 | n = (last_n + num / last_n) / 2.0; 26 | } 27 | n as i32 28 | } 29 | 30 | #[test] 31 | fn test_my_sqrt() { 32 | for _ in 0..10_u32.pow(6) { 33 | let random_i32 = crate::code_snippets::random_i32::random_i32(); 34 | assert_eq!(f64::from(random_i32).sqrt() as i32, my_sqrt(random_i32)); 35 | } 36 | } 37 | 38 | /// https://leetcode.com/problems/valid-perfect-square/ 39 | fn is_perfect_square(num: i32) -> bool { 40 | my_sqrt(num).pow(2) == num 41 | } 42 | -------------------------------------------------------------------------------- /src/random/impl_rand10_by_rand7.rs: -------------------------------------------------------------------------------- 1 | /// gen range 1..=7 2 | fn rand7() -> i32 { 3 | crate::code_snippets::random_i32::rand_range(1, 7) 4 | } 5 | 6 | /** https://leetcode.com/problems/implement-rand10-using-rand7/ 7 | ## Rejection Sampling 8 | 可以理解成将 2位7进制数 转为 1位10进制 9 | 因为至少要两位7进制数才能表达一位10进制 10 | b-1\a 1 2 3 4 5 6 7 11 | 0 1 2 3 4 5 6 7 12 | 1 8 9 101 2 3 4 13 | 2 5 6 7 8 9 101 14 | 3 2 3 4 5 6 7 8 15 | 4 9 101 2 3 4 5 16 | 5 6 7 8 9 10* * 17 | 6 * * * * * * * 18 | 19 | *表示拒绝掉的抽样,有9/49的概率被拒绝(41..=49),所以叫拒绝抽样 20 | 所以两次rand7()得到的2位7进制数能模拟1..=40的十进制范围, 21 | */ 22 | fn rand10() -> i32 { 23 | loop { 24 | let (a, b) = (rand7(), rand7()); 25 | // b是十位,(b-1)是为了偏移一下,从[1,7]偏移到[0,6],因为想10位的系数从0开始算 26 | let decimal = a + (b - 1) * 7; 27 | if decimal <= 40 { 28 | break decimal % 10 + 1; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/random/mod.rs: -------------------------------------------------------------------------------- 1 | mod impl_rand10_by_rand7; 2 | mod random_pick_index; 3 | -------------------------------------------------------------------------------- /src/random/random_pick_index.rs: -------------------------------------------------------------------------------- 1 | //! https://leetcode.com/problems/random-pick-index/ 2 | //! 相似题目: random_pick_index_with_weight, random_pick_index_with_blacklist, linked_list_random_node 3 | //! 如果nums中存在多个target,则等概率地随机返回一个满足nums[i]=target的下标i 4 | //! 题目给出的输入数据像是online_data/stream_data,数据流很大 5 | //! 关键是`new`的调用次数会比`pick`多,所以应当均摊时间复杂度,优化new而不优化pick 6 | //! 所以将输入的nums做成一个counter对于这题的测试用例还说是最慢的解法 7 | 8 | #[allow(non_camel_case_types)] 9 | type time_t = isize; 10 | 11 | struct RandomPickIndex { 12 | nums: Vec, 13 | } 14 | 15 | impl RandomPickIndex { 16 | fn new(nums: Vec) -> Self { 17 | Self { nums } 18 | } 19 | 20 | fn pick(&mut self, target: i32) -> i32 { 21 | extern "C" { 22 | fn rand() -> i32; 23 | } 24 | let mut count = 0_i32; 25 | let mut ret = 0_usize; 26 | for (i, num) in self.nums.iter().enumerate() { 27 | if target.ne(num) { 28 | continue; 29 | } 30 | count += 1; 31 | // 蓄水池抽样: 以1/n的概率更新return_value(留下当前的数据) 或 (n-1)/n不更新return_value(继续用之前的ret的值) 32 | // n为数据流(online_data,stream_data)中过去数据里值等于target的个数(也就是count变量) 33 | if unsafe { rand() } % count == 0 { 34 | ret = i; 35 | } 36 | } 37 | ret as i32 38 | } 39 | } 40 | 41 | struct RandomPickIndexCounterSolution { 42 | nums_index: std::collections::HashMap>, 43 | } 44 | 45 | impl RandomPickIndexCounterSolution { 46 | fn new(nums: Vec) -> Self { 47 | let mut nums_index = std::collections::HashMap::new(); 48 | for (i, num) in nums.into_iter().enumerate() { 49 | nums_index 50 | .entry(num) 51 | .or_insert_with(Vec::new) 52 | .push(i as i32); 53 | } 54 | // leetcode对随机数的检查不严格,即便不用当前时间戳做随机数种子(srand,time)去提高随机性,依然能AC 55 | Self { nums_index } 56 | } 57 | 58 | /// 如果nums中存在多个target,则等概率地随机返回一个满足nums[i]=target的下标i 59 | fn pick(&mut self, target: i32) -> i32 { 60 | extern "C" { 61 | fn rand() -> i32; 62 | } 63 | let candidates = &self.nums_index[&target]; 64 | let random_number = unsafe { rand() }; 65 | candidates[random_number as usize % (candidates.len() + 1)] 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/two_sum_two_pointers/four_sum_2.rs: -------------------------------------------------------------------------------- 1 | /// https://leetcode.com/problems/4sum-ii/ 2 | fn four_sum_count(a: Vec, b: Vec, c: Vec, d: Vec) -> i32 { 3 | let mut pairs = std::collections::HashMap::new(); 4 | for num_a in a { 5 | for num_b in &b { 6 | *pairs.entry(num_a + num_b).or_default() += 1; 7 | } 8 | } 9 | let mut count = 0; 10 | for num_c in c { 11 | for num_d in &d { 12 | count += pairs.get(&(-num_c - num_d)).unwrap_or(&0); 13 | } 14 | } 15 | count 16 | } 17 | -------------------------------------------------------------------------------- /src/two_sum_two_pointers/mod.rs: -------------------------------------------------------------------------------- 1 | mod four_sum_2; 2 | mod two_sum; 3 | mod two_sum_relative; 4 | mod valid_triangle_number; 5 | -------------------------------------------------------------------------------- /src/two_sum_two_pointers/two_sum.rs: -------------------------------------------------------------------------------- 1 | /*! https://leetcode.com/problems/two-sum/ 2 | 本题主要是掌握使用(B)Tree Map的数据结构达到O(n)的时间复杂度 3 | bitwise_补码的解法收录在java_leetcode中 4 | 不记录暴力遍历求解的方法 5 | 6 | 阅读第一名的代码的收获: 7 | 1. 在函数中间return,题目只要求返回一对索引,不需要遍历完整个数组(浪费时间) 8 | 2. BTreeMap在.get()方法的性能上明显强于HashMap 9 | 理论上红黑树的时间复杂度为O(logN),散列的时间复杂度为O(1) 10 | 在JDK1.8中,HashMap的长度大于8时才会转为红黑树进行存储 11 | 在Java中TreeMap是自动排序的,因此插入/删除操作会牺牲性能 12 | 3. 函数的最后,如果测试用例没有匹配项的话,可以写unreachable!()或返回vec![] 13 | */ 14 | 15 | /** 16 | # Two's complement(补码)存储负数的解法 17 | 18 | 以4-bit(16进制)为例 19 | 20 | ## 时钟模型 21 | !由于1111+0001会溢出所有位清0,所以不断地+1相当于有16个刻度的时钟在顺时针转圈 22 | !!所以-7时钟倒着拨动7格相当于 正着拨动16-7格 23 | 24 | ## 0的状态重复了 25 | 用时钟模型的话,0有两种表示方法,正转360°或者负转360° 26 | 但是在数字电路中,一个值两种状态都能表示不仅是一个UB,而且还浪费了宝贵的状态 27 | 所以出现了 负数补码=反码+1 也就是让负数的从-0开始全部往后挪一位 28 | -0变成了-1,-7变成了-8,所以4bit寄存器有符号数的范围是[-8, 7] 29 | */ 30 | fn two_sum_bitwise(nums: Vec, target: i32) -> Vec { 31 | // 2047是input case的最大值,确保: 32 | // 1. a & bit_mode = a; 33 | // 2. -a & bit_mode = bit_mode-a+1 34 | const VOLUME: usize = 2048; 35 | const BIT_MODE: i32 = 2047; 36 | // on a 32 bit x86 computer, usize = u32, 37 | // while on x86_64 computers, usize = u64 38 | let mut sum_tracker: [usize; VOLUME] = [0; 2048]; 39 | let mut c: usize; 40 | for i in 0..nums.len() { 41 | // & BIT_MODE防止相减后出现负数索引 42 | // 例如:-4 & 2047 = 4 43 | c = ((target - nums[i]) & BIT_MODE) as usize; 44 | debug_assert!(c.ge(&0)); 45 | if sum_tracker[c] != 0 { 46 | return vec![(sum_tracker[c] - 1) as i32, i as i32]; 47 | } 48 | // 加1防止index=0时保存的记录被`result[c] != 0`拦截 49 | // & bitMode防止相减后出现负数索引(case twoSumBitWise2) 50 | sum_tracker[(nums[i] & BIT_MODE) as usize] = i + 1; 51 | } 52 | unreachable!() 53 | } 54 | 55 | fn two_sum_hashmap(nums: Vec, target: i32) -> Vec { 56 | // use BTreeMap? HashMap is not friendly for cache 57 | let mut sum_tracker = std::collections::HashMap::new(); 58 | for (i, num) in nums.into_iter().enumerate() { 59 | if sum_tracker.contains_key(&num) { 60 | return vec![sum_tracker[&num] as i32, i as i32]; 61 | } 62 | sum_tracker.insert(target - num, i); 63 | } 64 | unsafe { 65 | std::hint::unreachable_unchecked(); 66 | } 67 | } 68 | 69 | #[test] 70 | fn test_two_sum() { 71 | const TEST_CASES: [(&[i32], i32, &[i32]); 3] = [ 72 | (&[2, 7, 9, 11], 9, &[0, 1]), 73 | (&[-3, 4, 3, 90], 0, &[0, 2]), 74 | (&[0, 4, 3, 0], 0, &[0, 3]), 75 | ]; 76 | for (nums, target, expected) in TEST_CASES { 77 | assert_eq!(two_sum_bitwise(nums.to_vec(), target), expected.to_vec()); 78 | assert_eq!(two_sum_hashmap(nums.to_vec(), target), expected.to_vec()); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/two_sum_two_pointers/two_sum_relative.rs: -------------------------------------------------------------------------------- 1 | /// https://leetcode.com/problems/fair-candy-swap/ 2 | /// 其实就是two_sum两数之差的变种题 3 | fn fair_candy_swap(a: Vec, b: Vec) -> Vec { 4 | let a_sum: i32 = a.iter().sum(); 5 | let b_sum: i32 = b.iter().sum(); 6 | let avg_sum = (a_sum + b_sum) / 2; 7 | // a_sum + b - a = avg_sum 8 | // b - a = target 9 | let target = avg_sum - a_sum; 10 | // 现在就转为两数之差(two sum)类解法了 11 | let mut map = std::collections::HashMap::with_capacity(b.len()); 12 | for b in b { 13 | map.insert(b - target, b); 14 | } 15 | for a in a { 16 | if let Some(b) = map.get(&a) { 17 | return vec![a, *b]; 18 | } 19 | } 20 | unreachable!() 21 | } 22 | 23 | #[test] 24 | fn test_fair_candy_swap() { 25 | const TEST_CASES: [(&[i32], &[i32], &[i32]); 4] = [ 26 | (&[1, 1], &[2, 2], &[1, 2]), 27 | (&[1, 2], &[2, 3], &[1, 2]), 28 | (&[2], &[1, 3], &[2, 3]), 29 | (&[1, 2, 5], &[2, 4], &[5, 4]), 30 | ]; 31 | for (a, b, output) in TEST_CASES { 32 | assert_eq!(fair_candy_swap(a.to_vec(), b.to_vec()), output.to_vec()); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/two_sum_two_pointers/valid_triangle_number.rs: -------------------------------------------------------------------------------- 1 | /** https://leetcode.com/problems/valid-triangle-number/ 2 | 输入一个排序数组,请问从数组中取3个值能组成多少个不同的三角形 3 | 由于数组是有序的,满足nums[a] + nums[b] > nums[c]即可,转换成两数之和大于类型问题 4 | 这题有点像three sum,和三数之和类似,遍历时可变的双指针要在等式的左边,而固定不变的target放等式右边 5 | 如果本题固定a,双指针是b和c,那么一定会出现漏掉解的情况,所以等式右边的target不能是可变的 6 | */ 7 | fn triangle_number(mut nums: Vec) -> i32 { 8 | nums.sort_unstable(); 9 | let n = nums.len(); 10 | if n < 3 { 11 | return 0; 12 | } 13 | let mut ret = 0; 14 | 15 | // c是最长边的下标索引,a和b是较短边的索引 16 | // nums[a] <= nums[b] <= nums[c] 17 | for c in 2..n { 18 | let mut a = 0; 19 | let mut b = c - 1; 20 | while a < b { 21 | if nums[a] + nums[b] > nums[c] { 22 | // [3,4,6,7]中如果a(3) + b(6) > c(7),那么a右移一位的解4,6,7也满足条件 23 | // 这步是不会漏掉解的关键,参考two-sum-less-than-or-equal-to-target 24 | // 既然nums[left]+nums[right]>target,那么[left..right-1, right]的解都满足条件 25 | // count会算上固定b之后[a..b-1, b]的所有解,然后b-=1; 而two_sum_le一题是固定a之后[a,a+1..b]所有解,所以a+=1 26 | ret += b - a; // 批量数 27 | b -= 1; 28 | } else { 29 | // 不会重复计算4,6,7这个解两次,因为4,6,7被记入时,b左移一位,已经退出循环,可以代入一些很小的用例去推敲 30 | a += 1; 31 | } 32 | } 33 | } 34 | 35 | ret as i32 36 | } 37 | 38 | #[test] 39 | fn test_triangle_number() { 40 | /* 41 | 输入: [2,2,3,4] 42 | 输出: 3 43 | 有效的组合是: 44 | 2,3,4 (使用第一个 2) 45 | 2,3,4 (使用第二个 2) 46 | 2,2,3 47 | */ 48 | const TEST_CASES: [(&[i32], i32); 1] = [(&[2, 2, 3, 4], 3)]; 49 | for (input, output) in TEST_CASES { 50 | assert_eq!(triangle_number(input.to_vec()), output); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/uncategorized/contains_substr_kmp.rs: -------------------------------------------------------------------------------- 1 | /*! https://leetcode.com/problems/implement-strstr/ 2 | 如果直接调用标准库API,那这题可以简单的写成 3 | ```text 4 | match haystack.find(needle) { 5 | Some(index) => index as i32, 6 | None => -1, 7 | } 8 | ``` 9 | 实现C语言自带的strstr()以及Java的indexOf() API 10 | 功能:查找字符串中的一个子串的出现位置,类似于find()、contains() API 11 | 12 | ## KMP算法 13 | 相关知识:3. Longest Substring Without Repeating Characters 14 | 例如:ABABD中搜索ABD,KMP算法的优势在于发现第一个AB不合条件后,指针立即跳到第二个AB的A上, 15 | 有点像双指针滑动窗口遍历最长无重复子串中,遇到重复的子串时立即查表,去偏移尾指针 16 | 17 | KMP用到的数据结构很像动态规划的dp数组,实际上是dfa(确定有限状态机) 18 | */ 19 | 20 | /// 抄别人的kmp算法 21 | #[cfg(FALSE)] 22 | fn kmp(haystack: String, needle: String) -> i32 { 23 | let haystack = haystack.into_bytes(); 24 | let needle = needle.into_bytes(); 25 | let prefix = Self::prefix_arr(&needle); 26 | let mut needle_p = 0; 27 | let mut haystack_p = 0; 28 | loop { 29 | if needle_p >= needle.len() { 30 | return haystack_p as i32 - needle.len() as i32; 31 | } 32 | if haystack_p >= haystack.len() { 33 | break; 34 | } 35 | if needle[needle_p] == haystack[haystack_p] { 36 | needle_p += 1; 37 | haystack_p += 1; 38 | continue; 39 | } 40 | if needle_p == 0 { 41 | haystack_p += 1; 42 | continue; 43 | } 44 | needle_p = prefix[needle_p - 1]; 45 | } 46 | -1 47 | } 48 | 49 | /* 50 | 这个数组应该叫dp或dfa,叫pattern或部分匹配表也行 51 | "部分匹配值"就是"前缀"和"后缀"的最长的共有元素的长度。以"ABCDABD"为例, 52 | - "A"的前缀和后缀都为空集,共有元素的长度为0; 53 | - "AB"的前缀为[A],后缀为[B],共有元素的长度为0; 54 | - "ABC"的前缀为[A, AB],后缀为[BC, C],共有元素的长度0; 55 | - "ABCD"的前缀为[A, AB, ABC],后缀为[BCD, CD, D],共有元素的长度为0; 56 | - "ABCDA"的前缀为[A, AB, ABC, ABCD],后缀为[BCDA, CDA, DA, A],共有元素为"A",长度为1; 57 | - "ABCDAB"的前缀为[A, AB, ABC, ABCD, ABCDA],后缀为[BCDAB, CDAB, DAB, AB, B],共有元素为"AB",长度为2; 58 | - "ABCDABD"的前缀为[A, AB, ABC, ABCD, ABCDA, ABCDAB],后缀为[BCDABD, CDABD, DABD, ABD, BD, D],共有元素的长度为0。 59 | 所以最终的部分匹配表是 60 | A B C D A B D 61 | 0 0 0 0 1 2 0 62 | 63 | 发现不匹配时的移动位数 = 已匹配的字符数 - 最后一个已匹配字符对应的部分匹配值 64 | 例如 haystack=ABCDABE, needle=ABCDABC 65 | 匹配到E不满足时,会前移6-2(B)个位置,有点像双指针最长无重复子串的尾指针前移的情况 66 | */ 67 | //fn kmp_prefix_arr(_needle: &[u8]) -> Vec {} 68 | -------------------------------------------------------------------------------- /src/uncategorized/longest_non_repeated_substr.rs: -------------------------------------------------------------------------------- 1 | /** https://leetcode.com/problems/longest-substring-without-repeating-characters/ 2 | ## 暴力遍历 3 | 两层for循环,穷举出所有可能的字符串逐个进行检查,通过HashSet等数据结构存储已出现过(seen)的字符串来提升性能 4 | 5 | ## sliding_window 6 | 通过左右两个浮标,按右浮标遍历String时发现重复的字符串时,让左浮标往右移到重复字符串的位置,从而缩小搜索范围 7 | 时间复杂度是O(N),最坏的情况O(2N)左右浮标分别完整的遍历一次字符串 8 | 9 | ## (数据结构)ascii记录字符出现位置 10 | leetcode大部分输入字符串的题全是小写字母,用ASCII表的数组性能比HashMap要好 11 | 比HashMap更高效,用一个长度为128的数组,索引是ascii字母值,value是在字符串中的索引的存储结构效率最高 12 | 13 | ```python 14 | def length_of_longest_substring(s: str) -> int: 15 | size = len(s) 16 | left = 0 17 | max_len = 0 18 | table = [-1 for _ in range(128)] 19 | 20 | for right in range(size): 21 | ord_right = ord(s[right]) 22 | if table[ord_right] != -1: 23 | # 避免重复的字符「不在当前的移动窗口中」 24 | # max能确保左指针只会向前移动 25 | # 例如abba的用例,当right移到第二个a时,left在第二个b,此时虽有重复但是left不能往左移到第一个a,会导致计算的最大长度变大 26 | left = max(left, table[ord_right] + 1) 27 | table[ord_right] = right 28 | temp_max = right - left + 1 29 | if temp_max > max_len: 30 | max_len = temp_max 31 | return max_len 32 | ``` 33 | */ 34 | 35 | fn sliding_window_ascii(s: String) -> i32 { 36 | let s = s.into_bytes(); 37 | let len: i32 = s.len() as i32; 38 | if len <= 1 { 39 | return len; 40 | } 41 | // index: 索引表示 某小写字母 的ASCII值, value: 从窗函数的右边界往左看第一个 某小写字母的出现索引 42 | let mut ascii_char_occur_index = [-1_i32; 128]; 43 | let (mut left, mut right, mut max_len) = (0_i32, 0_i32, 0_i32); 44 | while right < len { 45 | let current_char = s[right as usize]; 46 | if ascii_char_occur_index[current_char as usize] != -1 { 47 | /* 48 | 例如abba的用例,end=2时(第二个b),start会跳到2 49 | slider.1指到3时(最后一个a),第二次出现重复时,重复的是a,ascii_table中字母a的索引是0 50 | 如果不进行判断start会后退到0+1 51 | max() prevent sliders.0's index go back (test case: abba) 52 | 避免重复的字符「不在当前的移动窗口中」 53 | */ 54 | left = left.max(ascii_char_occur_index[current_char as usize] + 1); 55 | } 56 | ascii_char_occur_index[current_char as usize] = right; 57 | max_len = max_len.max(right - left); 58 | right += 1; 59 | } 60 | max_len + 1 61 | } 62 | 63 | /// 即便用了性能不如ASCII数组的HashMap,依然是0ms的解法 64 | fn sliding_window_hashmap(s: String) -> i32 { 65 | let s = s.into_bytes(); 66 | let mut max_len = 0; 67 | let mut start = 0_usize; 68 | let mut map = std::collections::HashMap::new(); 69 | for (i, letter) in s.into_iter().enumerate() { 70 | if let Some(&left_index) = map.get(&letter) { 71 | start = start.max(left_index + 1); 72 | } 73 | // 必须要更新完左边界再去算window长度才是正确的 74 | max_len = max_len.max(i - start + 1); 75 | // insert or update 76 | map.insert(letter, i); 77 | } 78 | max_len as i32 79 | } 80 | 81 | const TEST_CASES: [(&str, i32); 7] = [ 82 | ("dvdf", 3), 83 | ("abcabcbb", 3), 84 | ("bbbbb", 1), 85 | ("pwwkew", 3), 86 | ("abcabcbb", 3), 87 | ("abba", 2), 88 | (" ", 1), 89 | ]; 90 | 91 | #[test] 92 | fn test_i32_ascii_table() { 93 | for (input, expected) in TEST_CASES { 94 | assert_eq!(sliding_window_ascii(input.to_string()), expected); 95 | } 96 | } 97 | 98 | #[test] 99 | fn test_sliding_window_hashmap() { 100 | for (input, expected) in TEST_CASES { 101 | assert_eq!(sliding_window_hashmap(input.to_string()), expected); 102 | } 103 | } 104 | 105 | #[test] 106 | fn aa() { 107 | let a = 1; 108 | dbg!(a); 109 | } 110 | -------------------------------------------------------------------------------- /src/uncategorized/mod.rs: -------------------------------------------------------------------------------- 1 | mod contains_substr_kmp; 2 | mod longest_non_repeated_substr; 3 | mod reverse_integer_checked_mul_overflow; 4 | mod string_in_place; 5 | -------------------------------------------------------------------------------- /src/uncategorized/reverse_integer_checked_mul_overflow.rs: -------------------------------------------------------------------------------- 1 | /*! https://leetcode.com/problems/reverse-integer/ 2 | & https://leetcode.com/problems/palindrome-number/ 3 | 4 | ```text 5 | https://twitter.com/ospopen/status/1322127786618748928 6 | 好喜欢Rust这种checked前缀的命名和设计,优雅解决了反转i32的溢出问题 7 | 不用像官方解答那样冗长的上限下限溢出判断,还有一堆数学公式推导出7和-8这两个晦涩难懂的临界值 8 | 9 | 与之相反的unchecked前缀的API都是unsafe的,例如unchecked_sub溢出就会运行时panicked at 'attempt to subtract with overflow' 10 | ``` 11 | */ 12 | 13 | /// 注意Python/Ruby不能这么写(abs()),因为 Python -1整除10都等于-1(div_euclid),会陷入死循环 14 | /// reverse_integer(-2147483648) may panic on debug build(because overflow-checks=true profile), but work good on release build 15 | fn reverse_integer(x: i32) -> i32 { 16 | /* 17 | || -> Option { 18 | let mut ret = 0_i32; 19 | while x.abs() != 0 { 20 | ret = ret.checked_mul(10)?.checked_add(x%10)?; 21 | x /= 10; 22 | } 23 | Some(ret) 24 | }().unwrap_or(0) 25 | */ 26 | fn helper(mut n: i32) -> Option { 27 | let mut ret = 0_i32; 28 | while n.abs() != 0 { 29 | ret = ret.checked_mul(10)?.checked_add(n % 10)?; 30 | n /= 10; 31 | } 32 | Some(ret) 33 | } 34 | helper(x).unwrap_or_default() 35 | } 36 | 37 | // use std::ops::ControlFlow; 38 | // fn reverse_integer_control_flow(mut n: i32) -> ControlFlow { 39 | // let mut ret = 0_i32; 40 | // while n.abs() != 0 { 41 | // ret = ret.checked_mul(10)?.checked_add(n % 10)?; 42 | // n /= 10; 43 | // } 44 | // Controlo 45 | // } 46 | 47 | /// Beware of overflow when you reverse the integer 48 | fn is_palindrome(x: i32) -> bool { 49 | if x < 0 { 50 | return false; 51 | } 52 | x == reverse_integer(x) 53 | } 54 | 55 | const fn is_palindrome_half_traverse(x: i32) -> bool { 56 | if x < 0 || (x % 10 == 0 && x != 0) { 57 | return false; 58 | } 59 | let mut num = x; 60 | let mut rev = 0; 61 | // 只会【遍历一半】,遍历到中间时rev和num的值就会相近 62 | while rev < num { 63 | rev = rev * 10 + num % 10; 64 | num /= 10; 65 | } 66 | // dbg!((x, num, rev)); 67 | rev == num || num == (rev / 10) 68 | } 69 | 70 | #[test] 71 | fn test_reverse() { 72 | assert_eq!(reverse_integer(-123), -321); 73 | } 74 | 75 | #[test] 76 | fn test_is_palindrome() { 77 | const TEST_CASES: [(i32, bool); 5] = [ 78 | (121, true), 79 | (-121, false), 80 | (10, false), 81 | (0, true), 82 | (1_000_000_001, true), 83 | ]; 84 | for (input, expected) in TEST_CASES { 85 | assert_eq!(is_palindrome(input), expected); 86 | assert_eq!(is_palindrome_half_traverse(input), expected); 87 | } 88 | } 89 | --------------------------------------------------------------------------------