From 6905961883448c97152017de3fb129a4a68fb9b2 Mon Sep 17 00:00:00 2001 From: Shengwei Li Date: Mon, 6 Nov 2023 22:40:48 -0500 Subject: [PATCH 01/16] copied from home --- 2sum.py | 0 3sum.py | 0 3sum_closest.py | 0 4sum.py | 9 ++- LICENSE | 0 LRU_cache.py | 0 README.md | 0 add_binary.py | 0 add_two_numbers.py | 0 anagrams.py | 2 +- balanced_binary_tree.py | 2 + best_time_to_buy_and_sell_stock.py | 0 best_time_to_buy_and_sell_stock_ii.py | 0 best_time_to_buy_and_sell_stock_iii.py | 2 +- binary_tree_inorder.py | 2 +- binary_tree_level_order_traversal.py | 0 binary_tree_level_order_traversal_ii.py | 0 binary_tree_maximum_path_sum.py | 0 binary_tree_postorder.py | 0 binary_tree_preorder.py | 0 binary_tree_zigzag_level_traversal.py | 0 candy.py | 0 climb_stairs.py | 0 clone_graph.py | 0 combination_sum.py | 0 combination_sum_ii.py | 0 combinations.py | 63 ++++++++++++------- ...ruct_binary_tree_from_inorder_postorder.py | 0 ...truct_binary_tree_from_preorder_inorder.py | 0 container_with_most_water.py | 0 copy_list_with_random_pointer.py | 0 count_and_say.py | 0 decode_ways.py | 0 distinct_subsequences.py | 0 divide_two_integers.py | 0 edit_distance.py | 0 evaluate_reverse_polish_notation.py | 0 first_missing_positive.py | 0 flatten_binary_tree.py | 0 gas_station.py | 0 generate_parentheses.py | 34 ++++++++++ gray_code.py | 0 insert_intervals.py | 0 insertion_sort_list.py | 0 integer_to_roman.py | 0 interleave_string.py | 0 jump_game.py | 0 jump_game_ii.py | 0 largest_rectangle_in_histogram.py | 0 leetcode.tmproj | 0 length_of_last_word.py | 0 letter_combinations_of_phone_num.py | 0 linked_list_cycle.py | 0 linked_list_cycle_ii.py | 0 longest_common_prefix.py | 0 longest_consecutive_sequence.py | 0 longest_palindrome_substring.py | 0 longest_substring_without_repeating_chars.py | 0 longest_valid_parentheses.py | 0 max_points_on_a_line.py | 0 maximal_rectangle.py | 0 maximum_depth_of_binary_tree.py | 0 maximum_subarray.py | 0 median_of_two_sorted_arrays.py | 0 merge_intervals.py | 0 merge_k_sorted_lists.py | 0 merge_sorted_array.py | 0 merge_two_sorted_lists.py | 0 minimal_window_string.py | 2 + minimum_depth_of_binary_tree.py | 0 minimum_path_sum.py | 0 multiply_strings.py | 0 n_queens.py | 0 n_queens_ii.py | 0 next_permutation.py | 0 palindrome_number.py | 0 palindrome_partition.py | 0 palindrome_patition_ii.py | 0 partition_list.py | 0 pascals_triangle.py | 0 pascals_triangle_ii.py | 0 path_sum.py | 0 path_sum_ii.py | 0 permutation_sequence.py | 0 permutations.py | 0 permutations_ii.py | 0 plus_one.py | 0 populate_next_right_pointers.py | 0 populate_next_right_pointers_ii.py | 0 pow.py | 40 +++++++++--- recover_binary_search_tree.py | 0 regex_matching.py | 0 remove_dups_from_sorted_array.py | 0 remove_dups_from_sorted_array_ii.py | 0 remove_dups_from_sorted_list.py | 0 remove_dups_from_sorted_list_ii.py | 0 remove_element.py | 0 remove_nth_node_from_end_of_list.py | 0 reorder_list.py | 0 restore_ip_address.py | 0 reverse_integer.py | 0 reverse_linked_list_ii.py | 0 reverse_nodes_in_k_group.py | 0 roman_to_integer.py | 0 rotate_image.py | 0 rotate_list.py | 0 same_tree.py | 0 scramble_string.py | 0 search_for_range.py | 0 search_in_2d_matrix.py | 0 search_in_rotated_array.py | 0 search_in_rotated_array_ii.py | 0 search_insert_position.py | 0 set_matrix_zeros.py | 0 simplify_path.py | 0 single_number.py | 0 single_number_ii.py | 0 sort_colors.py | 0 sort_list.py | 0 sorted_array_to_binary_tree.py | 0 sorted_list_to_binary_tree.py | 0 spiral_matrix.py | 0 spiral_matrix_ii.py | 0 sqrt.py | 0 strStr.py | 0 string_to_integer_atoi.py | 0 subsets.py | 0 subsets_ii.py | 0 substring_with_concatenation.py | 0 sudoku_solver.py | 0 sum_root_to_leaf_numbers.py | 0 surrounded_regions.py | 0 swap_nodes_in_pairs.py | 0 symmetric_tree.py | 0 text_justification.py | 0 trap_rain_water.py | 0 triangle.py | 0 unique_binary_search_tree.py | 0 unique_binary_search_tree_ii.py | 0 unique_path.py | 0 unique_path_ii.py | 0 valid_number.py | 0 valid_palindrome.py | 0 valid_parentheses.py | 0 valid_sudoku.py | 0 validate_binary_search_tree.py | 0 wildcard_matching.py | 0 word_break.py | 0 word_break_ii.py | 0 word_ladder.py | 0 word_ladder_ii.py | 0 word_search.py | 0 zigzag_conversion.py | 0 153 files changed, 119 insertions(+), 37 deletions(-) mode change 100644 => 100755 2sum.py mode change 100644 => 100755 3sum.py mode change 100644 => 100755 3sum_closest.py mode change 100644 => 100755 4sum.py mode change 100644 => 100755 LICENSE mode change 100644 => 100755 LRU_cache.py mode change 100644 => 100755 README.md mode change 100644 => 100755 add_binary.py mode change 100644 => 100755 add_two_numbers.py mode change 100644 => 100755 anagrams.py mode change 100644 => 100755 balanced_binary_tree.py mode change 100644 => 100755 best_time_to_buy_and_sell_stock.py mode change 100644 => 100755 best_time_to_buy_and_sell_stock_ii.py mode change 100644 => 100755 best_time_to_buy_and_sell_stock_iii.py mode change 100644 => 100755 binary_tree_inorder.py mode change 100644 => 100755 binary_tree_level_order_traversal.py mode change 100644 => 100755 binary_tree_level_order_traversal_ii.py mode change 100644 => 100755 binary_tree_maximum_path_sum.py mode change 100644 => 100755 binary_tree_postorder.py mode change 100644 => 100755 binary_tree_preorder.py mode change 100644 => 100755 binary_tree_zigzag_level_traversal.py mode change 100644 => 100755 candy.py mode change 100644 => 100755 climb_stairs.py mode change 100644 => 100755 clone_graph.py mode change 100644 => 100755 combination_sum.py mode change 100644 => 100755 combination_sum_ii.py mode change 100644 => 100755 combinations.py mode change 100644 => 100755 construct_binary_tree_from_inorder_postorder.py mode change 100644 => 100755 construct_binary_tree_from_preorder_inorder.py mode change 100644 => 100755 container_with_most_water.py mode change 100644 => 100755 copy_list_with_random_pointer.py mode change 100644 => 100755 count_and_say.py mode change 100644 => 100755 decode_ways.py mode change 100644 => 100755 distinct_subsequences.py mode change 100644 => 100755 divide_two_integers.py mode change 100644 => 100755 edit_distance.py mode change 100644 => 100755 evaluate_reverse_polish_notation.py mode change 100644 => 100755 first_missing_positive.py mode change 100644 => 100755 flatten_binary_tree.py mode change 100644 => 100755 gas_station.py mode change 100644 => 100755 generate_parentheses.py mode change 100644 => 100755 gray_code.py mode change 100644 => 100755 insert_intervals.py mode change 100644 => 100755 insertion_sort_list.py mode change 100644 => 100755 integer_to_roman.py mode change 100644 => 100755 interleave_string.py mode change 100644 => 100755 jump_game.py mode change 100644 => 100755 jump_game_ii.py mode change 100644 => 100755 largest_rectangle_in_histogram.py mode change 100644 => 100755 leetcode.tmproj mode change 100644 => 100755 length_of_last_word.py mode change 100644 => 100755 letter_combinations_of_phone_num.py mode change 100644 => 100755 linked_list_cycle.py mode change 100644 => 100755 linked_list_cycle_ii.py mode change 100644 => 100755 longest_common_prefix.py mode change 100644 => 100755 longest_consecutive_sequence.py mode change 100644 => 100755 longest_palindrome_substring.py mode change 100644 => 100755 longest_substring_without_repeating_chars.py mode change 100644 => 100755 longest_valid_parentheses.py mode change 100644 => 100755 max_points_on_a_line.py mode change 100644 => 100755 maximal_rectangle.py mode change 100644 => 100755 maximum_depth_of_binary_tree.py mode change 100644 => 100755 maximum_subarray.py mode change 100644 => 100755 median_of_two_sorted_arrays.py mode change 100644 => 100755 merge_intervals.py mode change 100644 => 100755 merge_k_sorted_lists.py mode change 100644 => 100755 merge_sorted_array.py mode change 100644 => 100755 merge_two_sorted_lists.py mode change 100644 => 100755 minimal_window_string.py mode change 100644 => 100755 minimum_depth_of_binary_tree.py mode change 100644 => 100755 minimum_path_sum.py mode change 100644 => 100755 multiply_strings.py mode change 100644 => 100755 n_queens.py mode change 100644 => 100755 n_queens_ii.py mode change 100644 => 100755 next_permutation.py mode change 100644 => 100755 palindrome_number.py mode change 100644 => 100755 palindrome_partition.py mode change 100644 => 100755 palindrome_patition_ii.py mode change 100644 => 100755 partition_list.py mode change 100644 => 100755 pascals_triangle.py mode change 100644 => 100755 pascals_triangle_ii.py mode change 100644 => 100755 path_sum.py mode change 100644 => 100755 path_sum_ii.py mode change 100644 => 100755 permutation_sequence.py mode change 100644 => 100755 permutations.py mode change 100644 => 100755 permutations_ii.py mode change 100644 => 100755 plus_one.py mode change 100644 => 100755 populate_next_right_pointers.py mode change 100644 => 100755 populate_next_right_pointers_ii.py mode change 100644 => 100755 pow.py mode change 100644 => 100755 recover_binary_search_tree.py mode change 100644 => 100755 regex_matching.py mode change 100644 => 100755 remove_dups_from_sorted_array.py mode change 100644 => 100755 remove_dups_from_sorted_array_ii.py mode change 100644 => 100755 remove_dups_from_sorted_list.py mode change 100644 => 100755 remove_dups_from_sorted_list_ii.py mode change 100644 => 100755 remove_element.py mode change 100644 => 100755 remove_nth_node_from_end_of_list.py mode change 100644 => 100755 reorder_list.py mode change 100644 => 100755 restore_ip_address.py mode change 100644 => 100755 reverse_integer.py mode change 100644 => 100755 reverse_linked_list_ii.py mode change 100644 => 100755 reverse_nodes_in_k_group.py mode change 100644 => 100755 roman_to_integer.py mode change 100644 => 100755 rotate_image.py mode change 100644 => 100755 rotate_list.py mode change 100644 => 100755 same_tree.py mode change 100644 => 100755 scramble_string.py mode change 100644 => 100755 search_for_range.py mode change 100644 => 100755 search_in_2d_matrix.py mode change 100644 => 100755 search_in_rotated_array.py mode change 100644 => 100755 search_in_rotated_array_ii.py mode change 100644 => 100755 search_insert_position.py mode change 100644 => 100755 set_matrix_zeros.py mode change 100644 => 100755 simplify_path.py mode change 100644 => 100755 single_number.py mode change 100644 => 100755 single_number_ii.py mode change 100644 => 100755 sort_colors.py mode change 100644 => 100755 sort_list.py mode change 100644 => 100755 sorted_array_to_binary_tree.py mode change 100644 => 100755 sorted_list_to_binary_tree.py mode change 100644 => 100755 spiral_matrix.py mode change 100644 => 100755 spiral_matrix_ii.py mode change 100644 => 100755 sqrt.py mode change 100644 => 100755 strStr.py mode change 100644 => 100755 string_to_integer_atoi.py mode change 100644 => 100755 subsets.py mode change 100644 => 100755 subsets_ii.py mode change 100644 => 100755 substring_with_concatenation.py mode change 100644 => 100755 sudoku_solver.py mode change 100644 => 100755 sum_root_to_leaf_numbers.py mode change 100644 => 100755 surrounded_regions.py mode change 100644 => 100755 swap_nodes_in_pairs.py mode change 100644 => 100755 symmetric_tree.py mode change 100644 => 100755 text_justification.py mode change 100644 => 100755 trap_rain_water.py mode change 100644 => 100755 triangle.py mode change 100644 => 100755 unique_binary_search_tree.py mode change 100644 => 100755 unique_binary_search_tree_ii.py mode change 100644 => 100755 unique_path.py mode change 100644 => 100755 unique_path_ii.py mode change 100644 => 100755 valid_number.py mode change 100644 => 100755 valid_palindrome.py mode change 100644 => 100755 valid_parentheses.py mode change 100644 => 100755 valid_sudoku.py mode change 100644 => 100755 validate_binary_search_tree.py mode change 100644 => 100755 wildcard_matching.py mode change 100644 => 100755 word_break.py mode change 100644 => 100755 word_break_ii.py mode change 100644 => 100755 word_ladder.py mode change 100644 => 100755 word_ladder_ii.py mode change 100644 => 100755 word_search.py mode change 100644 => 100755 zigzag_conversion.py diff --git a/2sum.py b/2sum.py old mode 100644 new mode 100755 diff --git a/3sum.py b/3sum.py old mode 100644 new mode 100755 diff --git a/3sum_closest.py b/3sum_closest.py old mode 100644 new mode 100755 diff --git a/4sum.py b/4sum.py old mode 100644 new mode 100755 index 1146111..4499ac7 --- a/4sum.py +++ b/4sum.py @@ -33,15 +33,18 @@ class Solution: def fourSum(self, nums, target): nums = sorted(nums) result = set() + # cache all the seen sub-sum of any two numbers prior to current cursor cache = collections.defaultdict(set) - for i in range(len(nums)): + for i in range(len(nums)): # more strictly -- for i in range(1, len(nums) - 1) + # find supplimentray sub-sum in cache for the sub-sum of current cursor and any number after it for j in range(i + 1, len(nums)): for half in cache[target - nums[i] - nums[j]]: result.add(tuple(list(half) + [nums[i], nums[j]])) - for j in range(i): - cache[nums[i] + nums[j]].add((nums[j], nums[i])) + # cache the sub-sum of current cursor and any number prior to it + for k in range(i): + cache[nums[i] + nums[k]].add((nums[k], nums[i])) return map(list, result) diff --git a/LICENSE b/LICENSE old mode 100644 new mode 100755 diff --git a/LRU_cache.py b/LRU_cache.py old mode 100644 new mode 100755 diff --git a/README.md b/README.md old mode 100644 new mode 100755 diff --git a/add_binary.py b/add_binary.py old mode 100644 new mode 100755 diff --git a/add_two_numbers.py b/add_two_numbers.py old mode 100644 new mode 100755 diff --git a/anagrams.py b/anagrams.py old mode 100644 new mode 100755 index 4540810..779ddc2 --- a/anagrams.py +++ b/anagrams.py @@ -18,7 +18,7 @@ def get_marker(s): # note: do not sort s; sorting stats is consistent time # since len(stats) is at most 26 - stats = dict((c, s.count(c)) for c in s) + stats = dict((c, s.count(c)) for c in s) # --> Counter(s) return ''.join('%s%s' % (key, stats[key]) for key in sorted(stats)) class Solution: diff --git a/balanced_binary_tree.py b/balanced_binary_tree.py old mode 100644 new mode 100755 index 27a82c6..19a84ba --- a/balanced_binary_tree.py +++ b/balanced_binary_tree.py @@ -21,6 +21,8 @@ # https://oj.leetcode.com/discuss/59/different-definitions-balanced-result-different-judgments def check_balance(node): + """Retrun -1 if unbalanced; if balanced, return the depth + """ if node == None: return 0 diff --git a/best_time_to_buy_and_sell_stock.py b/best_time_to_buy_and_sell_stock.py old mode 100644 new mode 100755 diff --git a/best_time_to_buy_and_sell_stock_ii.py b/best_time_to_buy_and_sell_stock_ii.py old mode 100644 new mode 100755 diff --git a/best_time_to_buy_and_sell_stock_iii.py b/best_time_to_buy_and_sell_stock_iii.py old mode 100644 new mode 100755 index 2bbe12b..2a7a91b --- a/best_time_to_buy_and_sell_stock_iii.py +++ b/best_time_to_buy_and_sell_stock_iii.py @@ -7,7 +7,7 @@ """ # https://oj.leetcode.com/problems/best-time-to-buy-and-sell-stock-iii/ -# tags: hard, array, logic, tricky, edge cases +# tags: hard, array, dp, logic, tricky, edge cases """ Say you have an array for which the ith element is the price of a given stock on day i. diff --git a/binary_tree_inorder.py b/binary_tree_inorder.py old mode 100644 new mode 100755 index 6d54f86..9c01e34 --- a/binary_tree_inorder.py +++ b/binary_tree_inorder.py @@ -26,7 +26,7 @@ confused what "{1,#,2,3}" means? > read more on how binary tree is serialized on OJ. """ -# http://leetcode.com/2010/04/binary-search-tree-in-order-traversal.html +# http://leetcode.com/2010/04/binary-search-tree-in-order-traversal.html (404) # Definition for a binary tree node # class TreeNode: diff --git a/binary_tree_level_order_traversal.py b/binary_tree_level_order_traversal.py old mode 100644 new mode 100755 diff --git a/binary_tree_level_order_traversal_ii.py b/binary_tree_level_order_traversal_ii.py old mode 100644 new mode 100755 diff --git a/binary_tree_maximum_path_sum.py b/binary_tree_maximum_path_sum.py old mode 100644 new mode 100755 diff --git a/binary_tree_postorder.py b/binary_tree_postorder.py old mode 100644 new mode 100755 diff --git a/binary_tree_preorder.py b/binary_tree_preorder.py old mode 100644 new mode 100755 diff --git a/binary_tree_zigzag_level_traversal.py b/binary_tree_zigzag_level_traversal.py old mode 100644 new mode 100755 diff --git a/candy.py b/candy.py old mode 100644 new mode 100755 diff --git a/climb_stairs.py b/climb_stairs.py old mode 100644 new mode 100755 diff --git a/clone_graph.py b/clone_graph.py old mode 100644 new mode 100755 diff --git a/combination_sum.py b/combination_sum.py old mode 100644 new mode 100755 diff --git a/combination_sum_ii.py b/combination_sum_ii.py old mode 100644 new mode 100755 diff --git a/combinations.py b/combinations.py old mode 100644 new mode 100755 index 7a5fd24..d7da127 --- a/combinations.py +++ b/combinations.py @@ -25,28 +25,6 @@ ] """ -# TODO: use explicit dp matrix instead of recursion -""" -pseudo-code - for j in [1..n]: - matrix[1][j] = [[j]] - - for i in [2..k]: # length of each combination - for j in [i..n]: # current number - for k in [i-1 .. j-1]: # loop through prior combinations for number < j - matrix[i][j] = [] - for each in matrix[i-1][k]: - each.append(j) - matrix[i][j].append(each) - - result = [] - # collect all combinations for sub_combinations from matrix[k][k..n] - for j in [k..n]: - result.extend(matrix[k][j]) -""" - -# TODO: dfs - class Solution: # @return a list of lists of integers def combine(self, n, k): @@ -68,3 +46,44 @@ def comb(array, k): return res return comb(range(1, n+1), k) + + +# TODO: use explicit dp matrix instead of recursion +""" +pseudo-code + for j in [1..n]: + matrix[1][j] = [[j]] + + for i in [2..k]: # length of each combination + for j in [i..n]: # current number + for t in [i-1 .. j-1]: # loop through prior combinations for number < j + matrix[i][j] = [] + for each in matrix[i-1][t]: + each.append(j) + matrix[i][j].append(each) + + result = [] + # collect all combinations for sub_combinations from matrix[k][k..n] + for j in [k..n]: + result.extend(matrix[k][j]) +""" + +# TODO: dfs + +# 04/13/2022 +def combine(n, k): + array = range(1, n + 1) + res = set() + + def comb(start, sub_arr): + if len(sub_arr) == k: + res.add(tuple(sub_arr)) + return + + for i in range(start, n): + sub_arr.append(array[i]) + comb(i + 1, sub_arr) + sub_arr.pop() + + comb(0, []) + return res diff --git a/construct_binary_tree_from_inorder_postorder.py b/construct_binary_tree_from_inorder_postorder.py old mode 100644 new mode 100755 diff --git a/construct_binary_tree_from_preorder_inorder.py b/construct_binary_tree_from_preorder_inorder.py old mode 100644 new mode 100755 diff --git a/container_with_most_water.py b/container_with_most_water.py old mode 100644 new mode 100755 diff --git a/copy_list_with_random_pointer.py b/copy_list_with_random_pointer.py old mode 100644 new mode 100755 diff --git a/count_and_say.py b/count_and_say.py old mode 100644 new mode 100755 diff --git a/decode_ways.py b/decode_ways.py old mode 100644 new mode 100755 diff --git a/distinct_subsequences.py b/distinct_subsequences.py old mode 100644 new mode 100755 diff --git a/divide_two_integers.py b/divide_two_integers.py old mode 100644 new mode 100755 diff --git a/edit_distance.py b/edit_distance.py old mode 100644 new mode 100755 diff --git a/evaluate_reverse_polish_notation.py b/evaluate_reverse_polish_notation.py old mode 100644 new mode 100755 diff --git a/first_missing_positive.py b/first_missing_positive.py old mode 100644 new mode 100755 diff --git a/flatten_binary_tree.py b/flatten_binary_tree.py old mode 100644 new mode 100755 diff --git a/gas_station.py b/gas_station.py old mode 100644 new mode 100755 diff --git a/generate_parentheses.py b/generate_parentheses.py old mode 100644 new mode 100755 index 0e4d3d1..bdfd096 --- a/generate_parentheses.py +++ b/generate_parentheses.py @@ -37,3 +37,37 @@ def generator(s, left, right): yield each return list(generator('', n, n)) + + +# 04/14/2022 --> faster +def gen_parantheses(n): + + res = set() + + def gen(holder, left, right): + if (right == n): + res.add(''.join(holder)) + return + + if (left < n): + holder.append('(') + gen(holder, left + 1, right) + holder.pop() + + if (right < left): + holder.append(')') + gen(holder, left, right + 1) + holder.pop() + + gen([], 0, 0) + + return res + + +""" +In [62]: timeit(generateParenthesis(10)) +35.8 ms ± 147 µs per loop (mean ± std. dev. of 7 runs, 10 loops each) + +In [63]: timeit(gen_parantheses(10)) +23.7 ms ± 89.8 µs per loop (mean ± std. dev. of 7 runs, 10 loops each) +""" diff --git a/gray_code.py b/gray_code.py old mode 100644 new mode 100755 diff --git a/insert_intervals.py b/insert_intervals.py old mode 100644 new mode 100755 diff --git a/insertion_sort_list.py b/insertion_sort_list.py old mode 100644 new mode 100755 diff --git a/integer_to_roman.py b/integer_to_roman.py old mode 100644 new mode 100755 diff --git a/interleave_string.py b/interleave_string.py old mode 100644 new mode 100755 diff --git a/jump_game.py b/jump_game.py old mode 100644 new mode 100755 diff --git a/jump_game_ii.py b/jump_game_ii.py old mode 100644 new mode 100755 diff --git a/largest_rectangle_in_histogram.py b/largest_rectangle_in_histogram.py old mode 100644 new mode 100755 diff --git a/leetcode.tmproj b/leetcode.tmproj old mode 100644 new mode 100755 diff --git a/length_of_last_word.py b/length_of_last_word.py old mode 100644 new mode 100755 diff --git a/letter_combinations_of_phone_num.py b/letter_combinations_of_phone_num.py old mode 100644 new mode 100755 diff --git a/linked_list_cycle.py b/linked_list_cycle.py old mode 100644 new mode 100755 diff --git a/linked_list_cycle_ii.py b/linked_list_cycle_ii.py old mode 100644 new mode 100755 diff --git a/longest_common_prefix.py b/longest_common_prefix.py old mode 100644 new mode 100755 diff --git a/longest_consecutive_sequence.py b/longest_consecutive_sequence.py old mode 100644 new mode 100755 diff --git a/longest_palindrome_substring.py b/longest_palindrome_substring.py old mode 100644 new mode 100755 diff --git a/longest_substring_without_repeating_chars.py b/longest_substring_without_repeating_chars.py old mode 100644 new mode 100755 diff --git a/longest_valid_parentheses.py b/longest_valid_parentheses.py old mode 100644 new mode 100755 diff --git a/max_points_on_a_line.py b/max_points_on_a_line.py old mode 100644 new mode 100755 diff --git a/maximal_rectangle.py b/maximal_rectangle.py old mode 100644 new mode 100755 diff --git a/maximum_depth_of_binary_tree.py b/maximum_depth_of_binary_tree.py old mode 100644 new mode 100755 diff --git a/maximum_subarray.py b/maximum_subarray.py old mode 100644 new mode 100755 diff --git a/median_of_two_sorted_arrays.py b/median_of_two_sorted_arrays.py old mode 100644 new mode 100755 diff --git a/merge_intervals.py b/merge_intervals.py old mode 100644 new mode 100755 diff --git a/merge_k_sorted_lists.py b/merge_k_sorted_lists.py old mode 100644 new mode 100755 diff --git a/merge_sorted_array.py b/merge_sorted_array.py old mode 100644 new mode 100755 diff --git a/merge_two_sorted_lists.py b/merge_two_sorted_lists.py old mode 100644 new mode 100755 diff --git a/minimal_window_string.py b/minimal_window_string.py old mode 100644 new mode 100755 index 0b30eca..ea4b119 --- a/minimal_window_string.py +++ b/minimal_window_string.py @@ -23,6 +23,8 @@ If there are multiple such windows, you are guaranteed that there will always be only one unique minimum window in S. """ +# https://leetcode.com/problems/minimum-window-substring/discuss/26804/12-lines-Python + """ Notes for 3 bugs while implementing: diff --git a/minimum_depth_of_binary_tree.py b/minimum_depth_of_binary_tree.py old mode 100644 new mode 100755 diff --git a/minimum_path_sum.py b/minimum_path_sum.py old mode 100644 new mode 100755 diff --git a/multiply_strings.py b/multiply_strings.py old mode 100644 new mode 100755 diff --git a/n_queens.py b/n_queens.py old mode 100644 new mode 100755 diff --git a/n_queens_ii.py b/n_queens_ii.py old mode 100644 new mode 100755 diff --git a/next_permutation.py b/next_permutation.py old mode 100644 new mode 100755 diff --git a/palindrome_number.py b/palindrome_number.py old mode 100644 new mode 100755 diff --git a/palindrome_partition.py b/palindrome_partition.py old mode 100644 new mode 100755 diff --git a/palindrome_patition_ii.py b/palindrome_patition_ii.py old mode 100644 new mode 100755 diff --git a/partition_list.py b/partition_list.py old mode 100644 new mode 100755 diff --git a/pascals_triangle.py b/pascals_triangle.py old mode 100644 new mode 100755 diff --git a/pascals_triangle_ii.py b/pascals_triangle_ii.py old mode 100644 new mode 100755 diff --git a/path_sum.py b/path_sum.py old mode 100644 new mode 100755 diff --git a/path_sum_ii.py b/path_sum_ii.py old mode 100644 new mode 100755 diff --git a/permutation_sequence.py b/permutation_sequence.py old mode 100644 new mode 100755 diff --git a/permutations.py b/permutations.py old mode 100644 new mode 100755 diff --git a/permutations_ii.py b/permutations_ii.py old mode 100644 new mode 100755 diff --git a/plus_one.py b/plus_one.py old mode 100644 new mode 100755 diff --git a/populate_next_right_pointers.py b/populate_next_right_pointers.py old mode 100644 new mode 100755 diff --git a/populate_next_right_pointers_ii.py b/populate_next_right_pointers_ii.py old mode 100644 new mode 100755 diff --git a/pow.py b/pow.py old mode 100644 new mode 100755 index 0708cdf..3e3cc68 --- a/pow.py +++ b/pow.py @@ -13,18 +13,12 @@ Implement pow(x, n). """ +# https://leetcode.com/problems/powx-n/discuss/19560/Shortest-Python-Guaranteed + # TODO: try bit manipulation -def recursive_pow(x, n): - if n == 0: return 1 - if n == 1: return x - half, rest = divmod(n, 2) - product = recursive_pow(x, half) - if rest == 0: - return product * product - return product * product * x - +# Solution 1 class Solution: # @param x, a float # @param n, a integer @@ -33,3 +27,31 @@ def pow(self, x, n): if n < 0: return 1.0 / recursive_pow(x, -n) return recursive_pow(x, n) + + @staticmethod + def recursive_pow(x, n): + if n == 0: return 1 + if n == 1: return x + half, rest = divmod(n, 2) + product = recursive_pow(x, half) + if rest == 0: + return product * product + return product * product * x + + +# Solution 2 +def pow(x, n): + if n == 0: + return 1 + if n < 0: + return 1.0 / pow(x, -n) + + res = 1 + while n: + if n % 2 == 1: + # x keeps tracking power of 2 to this point; + # for pow(pow(pow(x, 2) * x, 2), 2), when it reaches the most inner pow(x, 2), x = pow(pow(x, 2), 2) + res *= x + x *= x + n >>= 1 + return res \ No newline at end of file diff --git a/recover_binary_search_tree.py b/recover_binary_search_tree.py old mode 100644 new mode 100755 diff --git a/regex_matching.py b/regex_matching.py old mode 100644 new mode 100755 diff --git a/remove_dups_from_sorted_array.py b/remove_dups_from_sorted_array.py old mode 100644 new mode 100755 diff --git a/remove_dups_from_sorted_array_ii.py b/remove_dups_from_sorted_array_ii.py old mode 100644 new mode 100755 diff --git a/remove_dups_from_sorted_list.py b/remove_dups_from_sorted_list.py old mode 100644 new mode 100755 diff --git a/remove_dups_from_sorted_list_ii.py b/remove_dups_from_sorted_list_ii.py old mode 100644 new mode 100755 diff --git a/remove_element.py b/remove_element.py old mode 100644 new mode 100755 diff --git a/remove_nth_node_from_end_of_list.py b/remove_nth_node_from_end_of_list.py old mode 100644 new mode 100755 diff --git a/reorder_list.py b/reorder_list.py old mode 100644 new mode 100755 diff --git a/restore_ip_address.py b/restore_ip_address.py old mode 100644 new mode 100755 diff --git a/reverse_integer.py b/reverse_integer.py old mode 100644 new mode 100755 diff --git a/reverse_linked_list_ii.py b/reverse_linked_list_ii.py old mode 100644 new mode 100755 diff --git a/reverse_nodes_in_k_group.py b/reverse_nodes_in_k_group.py old mode 100644 new mode 100755 diff --git a/roman_to_integer.py b/roman_to_integer.py old mode 100644 new mode 100755 diff --git a/rotate_image.py b/rotate_image.py old mode 100644 new mode 100755 diff --git a/rotate_list.py b/rotate_list.py old mode 100644 new mode 100755 diff --git a/same_tree.py b/same_tree.py old mode 100644 new mode 100755 diff --git a/scramble_string.py b/scramble_string.py old mode 100644 new mode 100755 diff --git a/search_for_range.py b/search_for_range.py old mode 100644 new mode 100755 diff --git a/search_in_2d_matrix.py b/search_in_2d_matrix.py old mode 100644 new mode 100755 diff --git a/search_in_rotated_array.py b/search_in_rotated_array.py old mode 100644 new mode 100755 diff --git a/search_in_rotated_array_ii.py b/search_in_rotated_array_ii.py old mode 100644 new mode 100755 diff --git a/search_insert_position.py b/search_insert_position.py old mode 100644 new mode 100755 diff --git a/set_matrix_zeros.py b/set_matrix_zeros.py old mode 100644 new mode 100755 diff --git a/simplify_path.py b/simplify_path.py old mode 100644 new mode 100755 diff --git a/single_number.py b/single_number.py old mode 100644 new mode 100755 diff --git a/single_number_ii.py b/single_number_ii.py old mode 100644 new mode 100755 diff --git a/sort_colors.py b/sort_colors.py old mode 100644 new mode 100755 diff --git a/sort_list.py b/sort_list.py old mode 100644 new mode 100755 diff --git a/sorted_array_to_binary_tree.py b/sorted_array_to_binary_tree.py old mode 100644 new mode 100755 diff --git a/sorted_list_to_binary_tree.py b/sorted_list_to_binary_tree.py old mode 100644 new mode 100755 diff --git a/spiral_matrix.py b/spiral_matrix.py old mode 100644 new mode 100755 diff --git a/spiral_matrix_ii.py b/spiral_matrix_ii.py old mode 100644 new mode 100755 diff --git a/sqrt.py b/sqrt.py old mode 100644 new mode 100755 diff --git a/strStr.py b/strStr.py old mode 100644 new mode 100755 diff --git a/string_to_integer_atoi.py b/string_to_integer_atoi.py old mode 100644 new mode 100755 diff --git a/subsets.py b/subsets.py old mode 100644 new mode 100755 diff --git a/subsets_ii.py b/subsets_ii.py old mode 100644 new mode 100755 diff --git a/substring_with_concatenation.py b/substring_with_concatenation.py old mode 100644 new mode 100755 diff --git a/sudoku_solver.py b/sudoku_solver.py old mode 100644 new mode 100755 diff --git a/sum_root_to_leaf_numbers.py b/sum_root_to_leaf_numbers.py old mode 100644 new mode 100755 diff --git a/surrounded_regions.py b/surrounded_regions.py old mode 100644 new mode 100755 diff --git a/swap_nodes_in_pairs.py b/swap_nodes_in_pairs.py old mode 100644 new mode 100755 diff --git a/symmetric_tree.py b/symmetric_tree.py old mode 100644 new mode 100755 diff --git a/text_justification.py b/text_justification.py old mode 100644 new mode 100755 diff --git a/trap_rain_water.py b/trap_rain_water.py old mode 100644 new mode 100755 diff --git a/triangle.py b/triangle.py old mode 100644 new mode 100755 diff --git a/unique_binary_search_tree.py b/unique_binary_search_tree.py old mode 100644 new mode 100755 diff --git a/unique_binary_search_tree_ii.py b/unique_binary_search_tree_ii.py old mode 100644 new mode 100755 diff --git a/unique_path.py b/unique_path.py old mode 100644 new mode 100755 diff --git a/unique_path_ii.py b/unique_path_ii.py old mode 100644 new mode 100755 diff --git a/valid_number.py b/valid_number.py old mode 100644 new mode 100755 diff --git a/valid_palindrome.py b/valid_palindrome.py old mode 100644 new mode 100755 diff --git a/valid_parentheses.py b/valid_parentheses.py old mode 100644 new mode 100755 diff --git a/valid_sudoku.py b/valid_sudoku.py old mode 100644 new mode 100755 diff --git a/validate_binary_search_tree.py b/validate_binary_search_tree.py old mode 100644 new mode 100755 diff --git a/wildcard_matching.py b/wildcard_matching.py old mode 100644 new mode 100755 diff --git a/word_break.py b/word_break.py old mode 100644 new mode 100755 diff --git a/word_break_ii.py b/word_break_ii.py old mode 100644 new mode 100755 diff --git a/word_ladder.py b/word_ladder.py old mode 100644 new mode 100755 diff --git a/word_ladder_ii.py b/word_ladder_ii.py old mode 100644 new mode 100755 diff --git a/word_search.py b/word_search.py old mode 100644 new mode 100755 diff --git a/zigzag_conversion.py b/zigzag_conversion.py old mode 100644 new mode 100755 From 3b3a0debc3e88f93ababd80cc3e0128b4e0af9d7 Mon Sep 17 00:00:00 2001 From: Shengwei Li Date: Thu, 28 Mar 2024 17:42:36 -0400 Subject: [PATCH 02/16] created a folder for first 150 problems --- .../10_regex_matching.py | 0 .../11_container_with_most_water.py | 0 .../12_integer_to_roman.py | 0 .../138_copy_list_with_random_pointer.py | 4 +- 2sum.py => first150/1_2sum.py | 0 .../2_add_two_numbers.py | 0 ...ngest_substring_without_repeating_chars.py | 52 ++++++++++++++----- .../4_median_of_two_sorted_arrays.py | 0 .../5_longest_palindrome_substring.py | 0 .../6_zigzag_conversion.py | 1 + .../71_simplify_path.py | 3 ++ .../7_reverse_integer.py | 1 + .../84_largest_rectangle_in_histogram.py | 1 + .../88_merge_sorted_array.py | 4 +- .../8_string_to_integer_atoi.py | 0 .../9_palindrome_number.py | 0 16 files changed, 50 insertions(+), 16 deletions(-) rename regex_matching.py => first150/10_regex_matching.py (100%) rename container_with_most_water.py => first150/11_container_with_most_water.py (100%) rename integer_to_roman.py => first150/12_integer_to_roman.py (100%) rename copy_list_with_random_pointer.py => first150/138_copy_list_with_random_pointer.py (98%) rename 2sum.py => first150/1_2sum.py (100%) rename add_two_numbers.py => first150/2_add_two_numbers.py (100%) rename longest_substring_without_repeating_chars.py => first150/3_longest_substring_without_repeating_chars.py (59%) rename median_of_two_sorted_arrays.py => first150/4_median_of_two_sorted_arrays.py (100%) rename longest_palindrome_substring.py => first150/5_longest_palindrome_substring.py (100%) rename zigzag_conversion.py => first150/6_zigzag_conversion.py (96%) rename simplify_path.py => first150/71_simplify_path.py (94%) rename reverse_integer.py => first150/7_reverse_integer.py (95%) rename largest_rectangle_in_histogram.py => first150/84_largest_rectangle_in_histogram.py (97%) rename merge_sorted_array.py => first150/88_merge_sorted_array.py (95%) rename string_to_integer_atoi.py => first150/8_string_to_integer_atoi.py (100%) rename palindrome_number.py => first150/9_palindrome_number.py (100%) diff --git a/regex_matching.py b/first150/10_regex_matching.py similarity index 100% rename from regex_matching.py rename to first150/10_regex_matching.py diff --git a/container_with_most_water.py b/first150/11_container_with_most_water.py similarity index 100% rename from container_with_most_water.py rename to first150/11_container_with_most_water.py diff --git a/integer_to_roman.py b/first150/12_integer_to_roman.py similarity index 100% rename from integer_to_roman.py rename to first150/12_integer_to_roman.py diff --git a/copy_list_with_random_pointer.py b/first150/138_copy_list_with_random_pointer.py similarity index 98% rename from copy_list_with_random_pointer.py rename to first150/138_copy_list_with_random_pointer.py index 397790a..bd6b152 100755 --- a/copy_list_with_random_pointer.py +++ b/first150/138_copy_list_with_random_pointer.py @@ -1,9 +1,11 @@ #!/usr/bin/env python # encoding: utf-8 """ -linked_list_random_pointer.py +138. Copy List with Random Pointer Created by Shengwei on 2014-07-01, re-implemented on 2014-07-15. + +Meta: 11/10/2023 -- phone screen """ # https://oj.leetcode.com/problems/copy-list-with-random-pointer/ diff --git a/2sum.py b/first150/1_2sum.py similarity index 100% rename from 2sum.py rename to first150/1_2sum.py diff --git a/add_two_numbers.py b/first150/2_add_two_numbers.py similarity index 100% rename from add_two_numbers.py rename to first150/2_add_two_numbers.py diff --git a/longest_substring_without_repeating_chars.py b/first150/3_longest_substring_without_repeating_chars.py similarity index 59% rename from longest_substring_without_repeating_chars.py rename to first150/3_longest_substring_without_repeating_chars.py index 1ea0bf7..28e1317 100755 --- a/longest_substring_without_repeating_chars.py +++ b/first150/3_longest_substring_without_repeating_chars.py @@ -17,32 +17,56 @@ # https://oj.leetcode.com/discuss/6168/my-o-n-solution -# alternative: use dict instead of set to shortcut the second inner loop +#20240321 +class Solution(object): + def lengthOfLongestSubstring(self, s): + """ + :type s: str + :rtype: int + """ + left = right = 0 + maxl = 0 + cache = {} + # note: can be replaced with `enumerate` + while right < len(s): + # move `left` only when `cache[s[right]]` is greater than `left`; + # s[right] could be seen earlier before the `left` position + # note: "equal" must be included + if s[right] in cache and left <= cache[s[right]]: + maxl = max(maxl, right - left) + left = cache[s[right]] + 1 + cache[s[right]] = right + right += 1 + return max(maxl, len(s) - left) + +# 20140724 class Solution: # @return an integer def lengthOfLongestSubstring(self, s): - cache = {} - start = end = 0 - max_length = 0 + cache = {} + start = end = 0 + max_length = 0 - while end < len(s): - if s[end] in cache and cache[s[end]] >= start: - # the char s[end] exists in range [start, end), - # move start to the next char of prior `s[end]` - start = cache[s[end]] + 1 + while end < len(s): + if s[end] in cache and cache[s[end]] >= start: + # the char s[end] exists in range [start, end), + # move start to the next char of prior `s[end]` + start = cache[s[end]] + 1 - # update the index with latest one - cache[s[end]] = end - end += 1 + # update the index with latest one + cache[s[end]] = end + end += 1 - max_length = max(max_length, end - start) + max_length = max(max_length, end - start) - return max_length + return max_length ######### rudimentary version ######### +# alternative: use dict instead of set to shortcut the second inner loop + class Solution: # @return an integer def lengthOfLongestSubstring(self, s): diff --git a/median_of_two_sorted_arrays.py b/first150/4_median_of_two_sorted_arrays.py similarity index 100% rename from median_of_two_sorted_arrays.py rename to first150/4_median_of_two_sorted_arrays.py diff --git a/longest_palindrome_substring.py b/first150/5_longest_palindrome_substring.py similarity index 100% rename from longest_palindrome_substring.py rename to first150/5_longest_palindrome_substring.py diff --git a/zigzag_conversion.py b/first150/6_zigzag_conversion.py similarity index 96% rename from zigzag_conversion.py rename to first150/6_zigzag_conversion.py index bc52fc8..90a38bc 100755 --- a/zigzag_conversion.py +++ b/first150/6_zigzag_conversion.py @@ -7,6 +7,7 @@ """ # https://oj.leetcode.com/problems/zigzag-conversion/ +# tags: medium, string, logic, edge cases """ The string "PAYPALISHIRING" is written in a zigzag pattern on a given number of rows like this: (you may want to display this pattern in a fixed font for better legibility) diff --git a/simplify_path.py b/first150/71_simplify_path.py similarity index 94% rename from simplify_path.py rename to first150/71_simplify_path.py index 1d243cb..95297fe 100755 --- a/simplify_path.py +++ b/first150/71_simplify_path.py @@ -4,6 +4,9 @@ simplify_path.py Created by Shengwei on 2014-07-24. + +Used: +* Meta: https://www.1point3acres.com/bbs/thread-1026092-1-1.html """ # https://oj.leetcode.com/problems/simplify-path/ diff --git a/reverse_integer.py b/first150/7_reverse_integer.py similarity index 95% rename from reverse_integer.py rename to first150/7_reverse_integer.py index b403750..01864af 100755 --- a/reverse_integer.py +++ b/first150/7_reverse_integer.py @@ -7,6 +7,7 @@ """ # https://oj.leetcode.com/problems/reverse-integer/ +# tags: easy / medium, numbers, integer, edge cases """ Reverse digits of an integer. diff --git a/largest_rectangle_in_histogram.py b/first150/84_largest_rectangle_in_histogram.py similarity index 97% rename from largest_rectangle_in_histogram.py rename to first150/84_largest_rectangle_in_histogram.py index 0d91198..078fc5f 100755 --- a/largest_rectangle_in_histogram.py +++ b/first150/84_largest_rectangle_in_histogram.py @@ -42,5 +42,6 @@ def largestRectangleArea(self, heights): stack.append(index) + # reset heights back as the input heights.pop() return max_area diff --git a/merge_sorted_array.py b/first150/88_merge_sorted_array.py similarity index 95% rename from merge_sorted_array.py rename to first150/88_merge_sorted_array.py index 939fd55..28b201b 100755 --- a/merge_sorted_array.py +++ b/first150/88_merge_sorted_array.py @@ -1,9 +1,11 @@ #!/usr/bin/env python # encoding: utf-8 """ -merge_sorted_array.py +88. Merge Sorted Array Created by Shengwei on 2014-07-15. + +Meta: 11/10/2023 -- phone screen """ # https://oj.leetcode.com/problems/merge-sorted-array/ diff --git a/string_to_integer_atoi.py b/first150/8_string_to_integer_atoi.py similarity index 100% rename from string_to_integer_atoi.py rename to first150/8_string_to_integer_atoi.py diff --git a/palindrome_number.py b/first150/9_palindrome_number.py similarity index 100% rename from palindrome_number.py rename to first150/9_palindrome_number.py From c709607716baf92bb4bc76b994f7124bc9b53a79 Mon Sep 17 00:00:00 2001 From: Shengwei Li Date: Thu, 28 Mar 2024 17:43:07 -0400 Subject: [PATCH 03/16] updates to existing answers --- best_time_to_buy_and_sell_stock_iii.py | 2 +- binary_tree_inorder.py | 16 ++++++++++++++++ first_missing_positive.py | 1 + generate_parentheses.py | 19 ++++++++++++++++++- next_permutation.py | 7 ++++--- 5 files changed, 40 insertions(+), 5 deletions(-) diff --git a/best_time_to_buy_and_sell_stock_iii.py b/best_time_to_buy_and_sell_stock_iii.py index 2a7a91b..19d7455 100755 --- a/best_time_to_buy_and_sell_stock_iii.py +++ b/best_time_to_buy_and_sell_stock_iii.py @@ -26,7 +26,7 @@ 2. for two transactions, they are a) either the prior big one transaction, and another second-max profit transaction; b) or the two max profit transactions broken down from the first max profit transaction; -3. for tree transactions, ... +3. for three transactions, ... """ # https://oj.leetcode.com/discuss/1381/any-solutions-better-than-o-n-2 diff --git a/binary_tree_inorder.py b/binary_tree_inorder.py index 9c01e34..179f0ef 100755 --- a/binary_tree_inorder.py +++ b/binary_tree_inorder.py @@ -35,6 +35,22 @@ # self.left = None # self.right = None + +# 20231029 +def inorder(root): + cur = root + stack = [] + while stack or cur: + if cur: + stack.append(cur) + cur = cur.left + else: + node = stack.pop() + print(node.value) + cur = node.right + + + class Solution: # @param root, a tree node # @return a list of integers diff --git a/first_missing_positive.py b/first_missing_positive.py index faa2086..7a4fac1 100755 --- a/first_missing_positive.py +++ b/first_missing_positive.py @@ -7,6 +7,7 @@ """ # https://oj.leetcode.com/problems/first-missing-positive/ +# tags: hard, array, logic """ Given an unsorted integer array, find the first missing positive integer. diff --git a/generate_parentheses.py b/generate_parentheses.py index bdfd096..944614d 100755 --- a/generate_parentheses.py +++ b/generate_parentheses.py @@ -17,8 +17,25 @@ "((()))", "(()())", "(())()", "()(())", "()()()" """ -# CC 8.5 +# CC 9.6 +# DP: https://leetcode.com/problems/generate-parentheses/solutions/10369/clean-python-dp-solution/ + + +# 20231104 +def generate(count): + res = [] + def routine(holder, left, right): + if right == count: + res.append(holder.decode()) + if left < count: + routine(holder + b'(', left + 1, right) + if left > right: + routine(holder + b')', left, right + 1) + routine(bytearray('', 'utf-8'), 0, 0) + return res + +# generator: https://leetcode.com/problems/generate-parentheses/solutions/10096/4-7-lines-python/ class Solution: # @param an integer # @return a list of string diff --git a/next_permutation.py b/next_permutation.py index 42e044d..a38037a 100755 --- a/next_permutation.py +++ b/next_permutation.py @@ -7,7 +7,7 @@ """ # https://oj.leetcode.com/problems/next-permutation/ -# tags: medium, numbers, permutation, logic +# tags: medium / hard, numbers, permutation, logic """ Implement next permutation, which rearranges numbers into the lexicographically next greater permutation of numbers. @@ -22,9 +22,10 @@ 1,1,5 → 1,5,1 """ +# algo: # 1. search from right to left and stop at first number num(i) that is less than num(i+1) -# 2. switch num(i) with the least greater number in num[i:0] -# 3. reverse all the numbers in num[i:-1] +# 2. switch num(i) with the least greater in num[i:-1] (to its right) +# 3. reverse all the numbers in num[i:-1] (to its right) class Solution: # @param nums, a list of integer From 301432d3ad71988e24e5df43b1533ddb6e1d07d2 Mon Sep 17 00:00:00 2001 From: Shengwei Li Date: Thu, 28 Mar 2024 17:43:21 -0400 Subject: [PATCH 04/16] new problems --- .gitignore | 1 + ...inimum_Remove_to_Make_Valid_Parentheses.py | 93 ++++++++++ ...m_Number_of_Events_That_Can_Be_Attended.py | 121 +++++++++++++ 162_Find_Peak_Element.py | 77 ++++++++ 163_Missing_Ranges.py | 39 ++++ 1762_Buildings_With_an_Ocean_View_Level.py | 66 +++++++ 1891_Cutting_Ribbons.py | 84 +++++++++ 206_Reverse_Linked_List.py | 70 ++++++++ 207_Course_Schedule.py | 97 ++++++++++ 2090_K_Radius_Subarray_Averages.py | 118 +++++++++++++ 215_Kth_Largest_Element_in_an_Array.py | 52 ++++++ 230_Kth_Smallest_Element_in_a_BST.py | 46 +++++ 234_Palindrome_Linked_List.py | 60 +++++++ ...Lowest_Common_Ancestor_of_a_Binary_Tree.py | 119 +++++++++++++ 253_Meeting_Rooms_II.py | 42 +++++ 297_Serialize_and_Deserialize_Binary_Tree.py | 37 ++++ 301_Remove_Invalid_Parentheses.py | 87 +++++++++ 310_Minimum_Height_Trees.py | 166 ++++++++++++++++++ 329_Longest_Increasing_Path_in_a_Matrix.py | 71 ++++++++ 410_Split_Array_Largest_Sum.py | 70 ++++++++ 528_Random_Pick_with_Weight.py | 144 +++++++++++++++ 543_Diameter_of_Binary_Tree.py | 89 ++++++++++ 680_Valid_Palindrome_II.py | 83 +++++++++ 921_Minimum_Add_to_Make_Parentheses_Valid.py | 55 ++++++ 973_K_Closest_Points_to_Origin.py | 63 +++++++ 25 files changed, 1950 insertions(+) create mode 100644 .gitignore create mode 100644 1249_Minimum_Remove_to_Make_Valid_Parentheses.py create mode 100755 1353_Maximum_Number_of_Events_That_Can_Be_Attended.py create mode 100644 162_Find_Peak_Element.py create mode 100644 163_Missing_Ranges.py create mode 100644 1762_Buildings_With_an_Ocean_View_Level.py create mode 100644 1891_Cutting_Ribbons.py create mode 100644 206_Reverse_Linked_List.py create mode 100644 207_Course_Schedule.py create mode 100644 2090_K_Radius_Subarray_Averages.py create mode 100644 215_Kth_Largest_Element_in_an_Array.py create mode 100755 230_Kth_Smallest_Element_in_a_BST.py create mode 100644 234_Palindrome_Linked_List.py create mode 100755 236_Lowest_Common_Ancestor_of_a_Binary_Tree.py create mode 100755 253_Meeting_Rooms_II.py create mode 100755 297_Serialize_and_Deserialize_Binary_Tree.py create mode 100755 301_Remove_Invalid_Parentheses.py create mode 100644 310_Minimum_Height_Trees.py create mode 100755 329_Longest_Increasing_Path_in_a_Matrix.py create mode 100644 410_Split_Array_Largest_Sum.py create mode 100644 528_Random_Pick_with_Weight.py create mode 100644 543_Diameter_of_Binary_Tree.py create mode 100755 680_Valid_Palindrome_II.py create mode 100644 921_Minimum_Add_to_Make_Parentheses_Valid.py create mode 100755 973_K_Closest_Points_to_Origin.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e43b0f9 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.DS_Store diff --git a/1249_Minimum_Remove_to_Make_Valid_Parentheses.py b/1249_Minimum_Remove_to_Make_Valid_Parentheses.py new file mode 100644 index 0000000..7e10c23 --- /dev/null +++ b/1249_Minimum_Remove_to_Make_Valid_Parentheses.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +1249. Minimum Remove to Make Valid Parentheses + +Created by Shengwei on 2023-11-04. + +Used: +* Meta: https://www.1point3acres.com/bbs/thread-1026092-1-1.html +""" + +# https://leetcode.com/problems/minimum-remove-to-make-valid-parentheses/description/ +# tags: medium, string, logic + +""" +Given a string s of '(' , ')' and lowercase English characters. + +Your task is to remove the minimum number of parentheses ( '(' or ')', in any positions ) so that the resulting parentheses string is valid and return any valid string. + +Formally, a parentheses string is valid if and only if: + +It is the empty string, contains only lowercase characters, or +It can be written as AB (A concatenated with B), where A and B are valid strings, or +It can be written as (A), where A is a valid string. + + +Example 1: + +Input: s = "lee(t(c)o)de)" +Output: "lee(t(c)o)de" +Explanation: "lee(t(co)de)" , "lee(t(c)ode)" would also be accepted. +Example 2: + +Input: s = "a)b(c)d" +Output: "ab(c)d" +Example 3: + +Input: s = "))((" +Output: "" +Explanation: An empty string is also valid. + + +Constraints: + +1 <= s.length <= 105 +s[i] is either'(' , ')', or lowercase English letter. +""" + + +class Solution: + def minRemoveToMakeValid(self, s: str) -> str: + def process(array, left, right): + cursor = lcnt = 0 + for b in array: + if b == ord(left): + lcnt += 1 + if b == ord(right): + if lcnt: + lcnt -= 1 + else: + continue + array[cursor] = b + cursor += 1 + array[cursor:] = [] + + array = bytearray(s, 'utf-8') + process(array, '(', ')') + array = array[::-1] + process(array, ')', '(') + return array[::-1].decode('utf-8') + + + +# others' better way: +# 1. record all the positions for '(' +# 2. 1) if found matching ')', drop one position of '(' +# 2) if no '(' available, clear ')' +# 3. clear all '(' with positions still registered +class Solution: + def minRemoveToMakeValid(self, s: str) -> str: + s = list(s) + stack = [] + for i, ch in enumerate(s): + if ch == "(": + stack.append(i) + elif ch == ")": + if stack: + stack.pop() + else: + s[i] = "" + while stack: + s[stack.pop()] = "" + return "".join(s) diff --git a/1353_Maximum_Number_of_Events_That_Can_Be_Attended.py b/1353_Maximum_Number_of_Events_That_Can_Be_Attended.py new file mode 100755 index 0000000..002ffc9 --- /dev/null +++ b/1353_Maximum_Number_of_Events_That_Can_Be_Attended.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +1353. Maximum Number of Events That Can Be Attended + +Created by Shengwei on 2022-04-18. +""" + +# https://leetcode.com/problems/maximum-number-of-events-that-can-be-attended/ +# tags: medium, array, interval, heap + +""" +You are given an array of events where events[i] = [startDayi, endDayi]. Every event i starts at startDayi and ends at endDayi. + +You can attend an event i at any day d where startTimei <= d <= endTimei. You can only attend one event at any time d. + +Return the maximum number of events you can attend. + + +Example 1: + +Input: events = [[1,2],[2,3],[3,4]] +Output: 3 +Explanation: You can attend all the three events. +One way to attend them all is as shown. +Attend the first event on day 1. +Attend the second event on day 2. +Attend the third event on day 3. + +Example 2: + +Input: events= [[1,2],[2,3],[3,4],[1,2]] +Output: 4 + + +Constraints: + +1 <= events.length <= 105 +events[i].length == 2 +1 <= startDayi <= endDayi <= 105 +""" + +# https://www.1point3acres.com/bbs/thread-885226-1-1.html (FB) + +class Solution: + def maxEvents(self, events: List[List[int]]) -> int: + """Loop through all the events: + 1. if no ongoing meeting now, move current day to the start day of the next earliest meeting + 2. for all the meetings start from the current day, add end days to the priority queue + 3. 1) attend the meeting that ends the earliest if any meeting is ongoing (res += 1), + 2) remove end days in the queue that have ended by the current day + 4. move the current day to the next, and repeat + """ + + if not events: + return 0 + + import heapq + + sorted_events = sorted(events) + res = 0 + curr = 1 + end_days = [] + index = 0 + while index < len(events) or end_days: + if not end_days and curr <= sorted_events[index][0]: + curr = sorted_events[index][0] + + while index < len(events) and sorted_events[index][0] <= curr: # "<=" can be "==" + heapq.heappush(end_days, sorted_events[index][1]) + index += 1 + + if end_days: # no need to check because of the first "if" in the loop + heapq.heappop(end_days) + res += 1 + + while end_days and curr >= end_days[0]: + heapq.heappop(end_days) + + curr += 1 # move this up so no need to check "=" for the loop above + + return res + + +# slight improvements +class Solution: + def maxEvents(self, events: List[List[int]]) -> int: + """Loop through all the events: + 1. if no ongoing meeting now, move current day to the start day of the next earliest meeting + 2. for all the meetings start from the current day, add end days to the priority queue + 3. 1) attend the meeting that ends the earliest if any meeting is ongoing (res += 1), + 2) remove end days in the queue that have ended by the current day + 4. move the current day to the next, and repeat + """ + + if not events: + return 0 + + import heapq + + events.sort() + res = 0 + curr = 1 + end_days = [] + index, size = 0, len(events) + while index < size or end_days: + if not end_days and curr <= events[index][0]: + curr = events[index][0] + + while index < size and events[index][0] == curr: + heapq.heappush(end_days, events[index][1]) + index += 1 + + heapq.heappop(end_days) + res += 1 + curr += 1 + + while end_days and curr > end_days[0]: + heapq.heappop(end_days) + + return res diff --git a/162_Find_Peak_Element.py b/162_Find_Peak_Element.py new file mode 100644 index 0000000..be334bc --- /dev/null +++ b/162_Find_Peak_Element.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +162. Find Peak Element + +Created by Shengwei on 2023-10-08. + +Used: +* Meta: https://www.1point3acres.com/bbs/thread-1026967-1-1.html +""" + +# https://leetcode.com/problems/find-peak-element/description/ +# tags: medium, array, binary search + +""" +A peak element is an element that is strictly greater than its neighbors. + +Given a 0-indexed integer array nums, find a peak element, and return its index. If the array contains multiple peaks, return the index to any of the peaks. + +You may imagine that nums[-1] = nums[n] = -∞. In other words, an element is always considered to be strictly greater than a neighbor that is outside the array. + +You must write an algorithm that runs in O(log n) time. + +Example 1: + +Input: nums = [1,2,3,1] +Output: 2 +Explanation: 3 is a peak element and your function should return the index number 2. + +Example 2: + +Input: nums = [1,2,1,3,5,6,4] +Output: 5 +Explanation: Your function can return either index number 1 where the peak element is 2, or index number 5 where the peak element is 6. + + +Constraints: + +1 <= nums.length <= 1000 +-231 <= nums[i] <= 231 - 1 +nums[i] != nums[i + 1] for all valid i. +""" + +# O(log n) +# https://leetcode.com/problems/find-peak-element/solutions/1290642/intuition-behind-conditions-complete-explanation-diagram-binary-search/ +class Solution: + def findPeakElement(self, nums: List[int]) -> int: + if not nums: + return -1 + if len(nums) == 1 or nums[0] > nums[1]: + return 0 + if nums[-1] > nums[-2]: + return len(nums) - 1 + + left, right = 1, len(nums) - 2 + # note: need to double check the case when left == right + while left <= right: + mid = left + (right - left) // 2 + if nums[mid] > nums[mid - 1] and nums[mid] > nums[mid + 1]: + return mid + if nums[mid] < nums[mid + 1]: + left = mid + 1 + else: + right = mid - 1 + return -1 + +# linear +class Solution: + def findPeakElement(self, nums: List[int]) -> int: + left = -sys.maxsize * 2 + nums.append(left) + for i in range(len(nums) - 1): + if nums[i] > left and nums[i] > nums[i + 1]: + return i + left = nums[i] + # better - revert nums as it was: nums.pop() + return -1 diff --git a/163_Missing_Ranges.py b/163_Missing_Ranges.py new file mode 100644 index 0000000..06f5821 --- /dev/null +++ b/163_Missing_Ranges.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +163. Missing Ranges + +Created by Shengwei on 2023-10-28. +""" + +# Locked +# tags: easy, array, number + +""" +Given a sorted integer array nums, where the range of elements are in the inclusive range [lower, upper], return its missing ranges. + +Example: + +Input: nums = [0, 1, 3, 50, 75], lower = 0 and upper = 99, +Output: ["2", "4->49", "51->74", "76->99"] +""" + +def missing_range(array, low, high): + res = [] + s = e = low + + for n in array: + while e < n: + e += 1 + if s == e - 1: + res.append('{}'.format(s)) + elif s < e: + res.append('{}->{}'.format(s, e - 1)) + s = e = n + 1 + + if s == high: + res.append('{}'.format(s)) + elif s < high: + res.append('{}->{}'.format(s, high)) + + return res diff --git a/1762_Buildings_With_an_Ocean_View_Level.py b/1762_Buildings_With_an_Ocean_View_Level.py new file mode 100644 index 0000000..34244a6 --- /dev/null +++ b/1762_Buildings_With_an_Ocean_View_Level.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +1762. Buildings With an Ocean View Level + +Created by Shengwei on 2023-10-29. +""" + +# Locked: https://leetcode.ca/2021-04-14-1762-Buildings-With-an-Ocean-View/ +# tags: easy, array, number + +""" +There are n buildings in a line. You are given an integer array heights of size n that represents the heights of the buildings in the line. + +The ocean is to the right of the buildings. A building has an ocean view if the building can see the ocean without obstructions. Formally, a building has an ocean view if all the buildings to its right have a smaller height. + +Return a list of indices (0-indexed) of buildings that have an ocean view, sorted in increasing order. + +Example 1: + +Input: heights = [4,2,3,1] + +Output: [0,2,3] + +Explanation: Building 1 (0-indexed) does not have an ocean view because building 2 is taller. + +Example 2: + +Input: heights = [4,3,2,1] + +Output: [0,1,2,3] + +Explanation: All the buildings have an ocean view. + +Example 3: + +Input: heights = [1,3,2,4] + +Output: [3] + +Explanation: Only building 3 has an ocean view. + +Example 4: + +Input: heights = [2,2,2,2] + +Output: [3] + +Explanation: Buildings cannot see the ocean if there are buildings of the same height to its right. + +Constraints: + +1 <= heights.length <= 10^5 +1 <= heights[i] <= 10^9 +""" + +def with_view(heights): + if not heights: + return [] + max_height = heights[-1] - 1 + res = [] + for i in range(-1, -len(heights) - 1, -1): + if heights[i] > max_height: + res.append(i) + max_height = heights[i] + return [len(heights) + i for i in reversed(res)] diff --git a/1891_Cutting_Ribbons.py b/1891_Cutting_Ribbons.py new file mode 100644 index 0000000..c1a854c --- /dev/null +++ b/1891_Cutting_Ribbons.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +1891. Cutting Ribbons + +Created by Shengwei on 2023-10-08. + +Used: +* Meta: https://www.1point3acres.com/bbs/thread-1026967-1-1.html +""" + +# locked - https://leetcode.ca/2021-07-24-1891-Cutting-Ribbons/ +# tags: easy / medium, array, sorting, binary search + +""" +You are given an integer array ribbons, where ribbons[i] represents the length of the i-th ribbon, and an integer k. You may cut any of the ribbons into any number of segments of positive integer lengths, or perform no cuts at all. + +For example, if you have a ribbon of length 4, you can: +Keep the ribbon of length 4, +Cut it into one ribbon of length 3 and one ribbon of length 1, +Cut it into two ribbons of length 2, +Cut it into one ribbon of length 2 and two ribbons of length 1, or +Cut it into four ribbons of length 1. +Your goal is to obtain k ribbons of all the same positive integer length. You are allowed to throw away any excess ribbon as a result of cutting. + +Return the maximum possible positive integer length that you can obtain k ribbons of, or 0 if you cannot obtain k ribbons of the same length. + +Example 1: + +Input: ribbons = [9,7,5], k = 3 + +Output: 5 + +Explanation: + +Cut the first ribbon to two ribbons, one of length 5 and one of length 4. +Cut the second ribbon to two ribbons, one of length 5 and one of length 2. +Keep the third ribbon as it is. +Now you have 3 ribbons of length 5. + +Example 2: + +Input: ribbons = [7,5,9], k = 4 + +Output: 4 + +Explanation: + +Cut the first ribbon to two ribbons, one of length 4 and one of length 3. +Cut the second ribbon to two ribbons, one of length 4 and one of length 1. +Cut the third ribbon to three ribbons, two of length 4 and one of length 1. +Now you have 4 ribbons of length 4. + +Example 3: + +Input: ribbons = [5,7,9], k = 22 + +Output: 0 + +Explanation: You cannot obtain k ribbons of the same positive integer length. + +Constraints: + +1 <= ribbons.length <= 10^5 +1 <= ribbons[i] <= 10^5 +1 <= k <= 10^9 +""" + +def find(woods, k): + if not woods or k > sum(woods): + return 0 + + woods.sort() + # a bit different from regular binary search: max is inclusive + short, long = 1, woods[-1] + while short < long: + mid = (short + long) // 2 + count = sum(w // mid for w in woods) + # note: even count == k, it should continue trying longer size + if count >= k: + short = mid + 1 + else: + long = mid - 1 + return long diff --git a/206_Reverse_Linked_List.py b/206_Reverse_Linked_List.py new file mode 100644 index 0000000..7717e96 --- /dev/null +++ b/206_Reverse_Linked_List.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +206. Reverse Linked List + +Created by Shengwei on 2023-10-06. +""" + +# https://leetcode.com/problems/reverse-linked-list/description/ +# tags: easy / medium, linked-list, reverse + +""" +Given the head of a singly linked list, reverse the list, and return the reversed list. + +Example 1: + +Input: head = [1,2,3,4,5] +Output: [5,4,3,2,1] + +Example 2: + +Input: head = [1,2] +Output: [2,1] + +Example 3: + +Input: head = [] +Output: [] + + +Constraints: + +The number of nodes in the list is the range [0, 5000]. +-5000 <= Node.val <= 5000 + + +Follow up: A linked list can be reversed either iteratively or recursively. Could you implement both? +""" + +# Definition for singly-linked list. +# class ListNode(object): +# def __init__(self, x): +# self.val = x +# self.next = None + +# 20231106 +class Solution: + def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]: + rev, cur = None, head + while cur: + # same: rev, cur.next, cur = cur, rev, cur.next + rev, rev.next, cur = cur, rev, cur.next + return rev + + +# 20181008 +class Solution(object): + def reverseList(self, head): + """ + :type head: ListNode + :rtype: ListNode + """ + + pre, node = None, head + while node: + post = node.next + node.next = pre + pre, node = node, post + + return pre diff --git a/207_Course_Schedule.py b/207_Course_Schedule.py new file mode 100644 index 0000000..037825d --- /dev/null +++ b/207_Course_Schedule.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +207. Course Schedule + +Created by Shengwei on 2023-10-31. +""" + +# https://leetcode.com/problems/course-schedule/description/ +# tags: medium, topological sort, array, recursion + +""" +There are a total of numCourses courses you have to take, labeled from 0 to numCourses - 1. You are given an array prerequisites where prerequisites[i] = [ai, bi] indicates that you must take course bi first if you want to take course ai. + +For example, the pair [0, 1], indicates that to take course 0 you have to first take course 1. +Return true if you can finish all courses. Otherwise, return false. + + + +Example 1: + +Input: numCourses = 2, prerequisites = [[1,0]] +Output: true +Explanation: There are a total of 2 courses to take. +To take course 1 you should have finished course 0. So it is possible. +Example 2: + +Input: numCourses = 2, prerequisites = [[1,0],[0,1]] +Output: false +Explanation: There are a total of 2 courses to take. +To take course 1 you should have finished course 0, and to take course 0 you should also have finished course 1. So it is impossible. + + +Constraints: + +1 <= numCourses <= 2000 +0 <= prerequisites.length <= 5000 +prerequisites[i].length == 2 +0 <= ai, bi < numCourses +All the pairs prerequisites[i] are unique. +""" + +# Recursive (20231031) +class Solution: + def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool: + from collections import defaultdict + + courses = defaultdict(int) + deps = defaultdict(set) + for (course, pre) in prerequisites: + deps[pre].add(course) + courses.setdefault(pre, 0) + courses[course] += 1 + + completed = set() + def complete_course(course): + completed.add(course) + for dep in deps[course]: + courses[dep] -= 1 + if courses[dep] == 0: + complete_course(dep) + + for c, cnt in courses.items(): + # note: check if "c" has been processed in recursion + if c not in completed and cnt == 0: + complete_course(c) + + return all(cnt == 0 for cnt in courses.values()) + + +# Iterative (20220508) +class Solution: + def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool: + from collections import defaultdict, deque + + can_study = deque() + prereq = defaultdict(int) # value: number of prerequisites for course i + dep = defaultdict(set) # value: set of depending courses of course i + + for course, pre in prerequisites: + prereq[course] += 1 + dep[pre].add(course) + + for course in range(numCourses): + if course not in prereq: + can_study.append(course) + + while can_study: + course = can_study.popleft() + for depending in dep[course]: + if prereq[depending] == 1: + prereq.pop(depending) + can_study.append(depending) + else: + prereq[depending] -= 1 + + return len(prereq) == 0 diff --git a/2090_K_Radius_Subarray_Averages.py b/2090_K_Radius_Subarray_Averages.py new file mode 100644 index 0000000..97175c4 --- /dev/null +++ b/2090_K_Radius_Subarray_Averages.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +2090. K Radius Subarray Averages + +Created by Shengwei on 2023-11-05. + +Used: +* Meta: https://www.1point3acres.com/bbs/thread-1026011-1-1.html +""" + +# https://leetcode.com/problems/minimum-remove-to-make-valid-parentheses/description/ +# tags: medium, array, pointer, sum, sliding + +""" +You are given a 0-indexed array nums of n integers, and an integer k. + +The k-radius average for a subarray of nums centered at some index i with the radius k is the average of all elements in nums between the indices i - k and i + k (inclusive). If there are less than k elements before or after the index i, then the k-radius average is -1. + +Build and return an array avgs of length n where avgs[i] is the k-radius average for the subarray centered at index i. + +The average of x elements is the sum of the x elements divided by x, using integer division. The integer division truncates toward zero, which means losing its fractional part. + +For example, the average of four elements 2, 3, 1, and 5 is (2 + 3 + 1 + 5) / 4 = 11 / 4 = 2.75, which truncates to 2. + + +Example 1: + + +Input: nums = [7,4,3,9,1,8,5,2,6], k = 3 +Output: [-1,-1,-1,5,4,4,-1,-1,-1] +Explanation: +- avg[0], avg[1], and avg[2] are -1 because there are less than k elements before each index. +- The sum of the subarray centered at index 3 with radius 3 is: 7 + 4 + 3 + 9 + 1 + 8 + 5 = 37. + Using integer division, avg[3] = 37 / 7 = 5. +- For the subarray centered at index 4, avg[4] = (4 + 3 + 9 + 1 + 8 + 5 + 2) / 7 = 4. +- For the subarray centered at index 5, avg[5] = (3 + 9 + 1 + 8 + 5 + 2 + 6) / 7 = 4. +- avg[6], avg[7], and avg[8] are -1 because there are less than k elements after each index. +Example 2: + +Input: nums = [100000], k = 0 +Output: [100000] +Explanation: +- The sum of the subarray centered at index 0 with radius 0 is: 100000. + avg[0] = 100000 / 1 = 100000. +Example 3: + +Input: nums = [8], k = 100000 +Output: [-1] +Explanation: +- avg[0] is -1 because there are less than k elements before and after index 0. + + +Constraints: + +n == nums.length +1 <= n <= 105 +0 <= nums[i], k <= 105 +""" + + +class Solution: + def getAverages(self, nums: List[int], k: int) -> List[int]: + count = 2 * k + 1 + res = [-1] * len(nums) + if count > len(nums): + return res + + sub_sum = sum(nums[:count]) + # low = high - count; cursor = high - k - 1 + # low, high, cursor = 0, count, k + res[k] = sub_sum // count # process item first + for index in range(count, len(nums)): + sub_sum += nums[index] - nums[index - count] + res[index - k] = sub_sum // count + + return res + + +# slightly updated for last-one case +class Solution: + def getAverages(self, nums: List[int], k: int) -> List[int]: + count = 2 * k + 1 + res = [-1] * len(nums) + if count > len(nums): + return res + + sub_sum = sum(nums[:count]) + # low = high - count; cursor = high - k - 1 + low, high, cursor = 0, count, k + while high < len(nums): + res[cursor] = sub_sum // count + cursor += 1 + + sub_sum += nums[high] - nums[low] + high += 1 + low += 1 + res[cursor] = sub_sum // count + + return res + + + +class Solution: + def getAverages(self, nums: List[int], k: int) -> List[int]: + count = 2 * k + 1 + sub_sum = sum(nums[:count]) + low, high = 0, count + res = [-1] * len(nums) + for cursor in range(k, len(nums) - k, 1): + res[cursor] = sub_sum // count + if high < len(nums): + sub_sum += nums[high] - nums[low] + high += 1 + low += 1 + + return res + diff --git a/215_Kth_Largest_Element_in_an_Array.py b/215_Kth_Largest_Element_in_an_Array.py new file mode 100644 index 0000000..f5c6702 --- /dev/null +++ b/215_Kth_Largest_Element_in_an_Array.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +215. Kth Largest Element in an Array + +Created by Shengwei on 2023-10-08. + +Used: +* Meta: https://www.1point3acres.com/bbs/thread-1026967-1-1.html +""" + +# https://leetcode.com/problems/kth-largest-element-in-an-array/description/ +# tags: easy / medium, array, sorting, heap + +""" +Given an integer array nums and an integer k, return the kth largest element in the array. + +Note that it is the kth largest element in the sorted order, not the kth distinct element. + +Can you solve it without sorting? + + +Example 1: + +Input: nums = [3,2,1,5,6,4], k = 2 +Output: 5 + +Example 2: + +Input: nums = [3,2,3,1,2,4,5,5,6], k = 4 +Output: 4 + + +Constraints: + +1 <= k <= nums.length <= 105 +-104 <= nums[i] <= 104 +""" + +# quick select: https://leetcode.com/problems/kth-largest-element-in-an-array/solutions/1019513/python-quickselect-average-o-n-explained/ + + +class Solution: + def findKthLargest(self, nums: List[int], k: int) -> int: + import heapq + heap = nums[:k] + heapq.heapify(heap) + + for i in range(k, len(nums)): + heapq.heappushpop(heap, nums[i]) + + return heap[0] if heap else -1 diff --git a/230_Kth_Smallest_Element_in_a_BST.py b/230_Kth_Smallest_Element_in_a_BST.py new file mode 100755 index 0000000..ff3bbb9 --- /dev/null +++ b/230_Kth_Smallest_Element_in_a_BST.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +230. Kth Smallest Element in a BST + +Created by Shengwei on 2022-04-25. +""" + +# https://leetcode.com/problems/kth-smallest-element-in-a-bst/ +# tags: easy / medium, tree, inorder, kth + +""" +Given the root of a binary search tree, and an integer k, return the kth smallest value (1-indexed) of all the values of the nodes in the tree. + + +Example 1: +Input: root = [3,1,4,null,2], k = 1 +Output: 1 + +Example 2: +Input: root = [5,3,6,2,4,null,null,1], k = 3 +Output: 3 + +""" + +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, val=0, left=None, right=None): +# self.val = val +# self.left = left +# self.right = right +class Solution: + def kthSmallest(self, root: Optional[TreeNode], k: int) -> int: + cursor = root + stack = [] + while cursor or stack: + if cursor: + stack.append(cursor) + cursor = cursor.left + else: + node = stack.pop() + k -= 1 + if not k: + return node.val + cursor = node.right + return -1 diff --git a/234_Palindrome_Linked_List.py b/234_Palindrome_Linked_List.py new file mode 100644 index 0000000..3276dcf --- /dev/null +++ b/234_Palindrome_Linked_List.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +234. Palindrome Linked List + +Created by Shengwei on 2023-10-06. + +Used: +* Meta: https://www.1point3acres.com/bbs/thread-1026007-1-1.html +""" + +# https://leetcode.com/problems/palindrome-linked-list/description/ +# tags: easy / medium, linked-list, palindrome, reverse + +""" +Given the head of a singly linked list, return true if it is a palindrome or false otherwise. + +Example 1: + +Input: head = [1,2,2,1] +Output: true + +Example 2: + +Input: head = [1,2] +Output: false + + +Constraints: + +The number of nodes in the list is in the range [1, 105]. +0 <= Node.val <= 9 + + +Follow up: Could you do it in O(n) time and O(1) space? +""" + +# https://leetcode.com/problems/palindrome-linked-list/solutions/64500/11-lines-12-with-restore-o-n-time-o-1-space/ + +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, val=0, next=None): +# self.val = val +# self.next = next +class Solution: + def isPalindrome(self, head: Optional[ListNode]) -> bool: + rev = None + slow = fast = head + while fast and fast.next: + fast = fast.next.next + rev, rev.next, slow = slow, rev, slow.next + + # length of list is odd + if fast: + slow = slow.next + + while rev and rev.val == slow.val: + rev = rev.next + slow = slow.next + return not rev \ No newline at end of file diff --git a/236_Lowest_Common_Ancestor_of_a_Binary_Tree.py b/236_Lowest_Common_Ancestor_of_a_Binary_Tree.py new file mode 100755 index 0000000..2fcec71 --- /dev/null +++ b/236_Lowest_Common_Ancestor_of_a_Binary_Tree.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +236. Lowest Common Ancestor of a Binary Tree + +Created by Shengwei on 2022-04-17. +""" + +# https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-tree/ +# tags: easy / medium, tree, recursion + +""" +Given a binary tree, find the lowest common ancestor (LCA) of two given nodes in the tree. + +According to the definition of LCA on Wikipedia: “The lowest common ancestor is defined between two nodes p and q as the lowest node in T that has both p and q as descendants (where we allow a node to be a descendant of itself).” + +Example 1: +Input: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1 +Output: 3 +Explanation: The LCA of nodes 5 and 1 is 3. + +Example 2: +Input: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4 +Output: 5 +Explanation: The LCA of nodes 5 and 4 is 5, since a node can be a descendant of itself according to the LCA definition. + +Example 3: +Input: root = [1,2], p = 1, q = 2 +Output: 1 +""" + +# 2023-10-28 +class Solution: + def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode': + if not root: + return None + + def routine(node: 'TreeNode') -> 'TreeNode': + # note: this can be optimized to check if input node is None + res_left = routine(node.left) if node.left else None + if res_left and res_left not in (p, q): + return res_left + + res_right = routine(node.right) if node.right else None + if res_right and res_right not in (p, q): + return res_right + + if res_left and res_right: + return node + + # note: this can be moved above to the beginning + if node in (p, q): + return node + + return res_left or res_right + + return routine(root) + + +# 2022/04 (too many edge cases, better to use the earlier one) +class Solution: + def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode': + """Finds the least common ancestor of node1 and node2 in the tree of given root. + + Returns: + - None if not found + - root if 1. each of (node1, node2) found in root's left and right subtrees + or 2. one of (node1, node2) found in root's children and root is the another + - another node returned from root's left or right subtrees, as it's found earlier + - node1 or node2 if only one node is found in the tree of given root + """ + if root is None: + return + + left = self.lowestCommonAncestor(root.left, p, q) + right = self.lowestCommonAncestor(root.right, p, q) + # print("node:", root, "left:", left, "right:", right) + all_set = (left, right, root) + if p in all_set and q in all_set: + return root + + def is_parent_node(node): + return node and node != p and node != q + + if is_parent_node(left): + return left + if is_parent_node(right): + return right + + def check(node): + if node in (p, q): + return node + + return check(left) or check(right) or check(root) + + +# 2019/11 +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, x): +# self.val = x +# self.left = None +# self.right = None +class Solution: + def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode': + + def check(node: 'TreeNode') -> 'TreeNode': + if not node: + return + if node == p or node == q: + return node + + left, right = check(node.left), check(node.right) + if left and right: + return node + + return left or right + + return check(root) diff --git a/253_Meeting_Rooms_II.py b/253_Meeting_Rooms_II.py new file mode 100755 index 0000000..b277439 --- /dev/null +++ b/253_Meeting_Rooms_II.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +253. Meeting Rooms II + +Created by Shengwei on 2022-04-21. +""" + +# locked +# tags: medium, array, interval, heap + +""" +Given an array of meeting time intervals consisting of start and end times [[s1,e1],[s2,e2],...] (si < ei), find the minimum number of conference rooms required. + +Example 1: + +Input: [[0, 30],[5, 10],[15, 20]] +Output: 2 +Example 2: + +Input: [[7,10],[2,4]] +Output: 1 +NOTE: input types have been changed on April 15, 2019. Please reset to default code definition to get new method signature. + + +""" + +class Solution: + def minMeetingRooms(self, intervals: List[List[int]]) -> int: + import heapq + + end_heap = [] + for start, end in sorted(intervals): + # if there is "at least one" meeting that has ended prior to current meeting, + # pop it out to reuse the meeting room; + # don't bother other meetings and let the meeting rooms be occupied, + # so it's easier to count how many rooms needed at maximum + if end_heap and start > end_heap[0]: + heapq.heappop(end_heap) + heapq.heappush(end_heap, end) + + return len(end_heap) diff --git a/297_Serialize_and_Deserialize_Binary_Tree.py b/297_Serialize_and_Deserialize_Binary_Tree.py new file mode 100755 index 0000000..e84b602 --- /dev/null +++ b/297_Serialize_and_Deserialize_Binary_Tree.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +297. Serialize and Deserialize Binary Tree + +Created by Shengwei on 2022-04-25. +""" + +# https://leetcode.com/problems/serialize-and-deserialize-binary-tree/ +# tags: medium / hard, tree, recursive, string + +""" +Serialization is the process of converting a data structure or object into a sequence of bits so that it can be stored in a file or memory buffer, or transmitted across a network connection link to be reconstructed later in the same or another computer environment. + +Design an algorithm to serialize and deserialize a binary tree. There is no restriction on how your serialization/deserialization algorithm should work. You just need to ensure that a binary tree can be serialized to a string and this string can be deserialized to the original tree structure. + +Clarification: The input/output format is the same as how LeetCode serializes a binary tree. You do not necessarily need to follow this format, so please be creative and come up with different approaches yourself. + + +Example 1: + +Input: root = [1,2,3,null,null,4,5] +Output: [1,2,3,null,null,4,5] + +Example 2: + +Input: root = [] +Output: [] + + +Constraints: + +The number of nodes in the tree is in the range [0, 104]. +-1000 <= Node.val <= 1000 +""" + +# https://leetcode.com/problems/serialize-and-deserialize-binary-tree/discuss/74259/Recursive-preorder-Python-and-C%2B%2B-O(n) diff --git a/301_Remove_Invalid_Parentheses.py b/301_Remove_Invalid_Parentheses.py new file mode 100755 index 0000000..5457241 --- /dev/null +++ b/301_Remove_Invalid_Parentheses.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +301. Remove Invalid Parentheses + +Created by Shengwei on 2022-04-21. +""" + +# https://leetcode.com/problems/remove-invalid-parentheses/ +# tags: medium, string, dfs, bfs + +""" +Given a string s that contains parentheses and letters, remove the minimum number of invalid parentheses to make the input string valid. + +Return all the possible results. You may return the answer in any order. + + +Example 1: + +Input: s = "()())()" +Output: ["(())()","()()()"] + +Example 2: + +Input: s = "(a)())()" +Output: ["(a())()","(a)()()"] + +Example 3: + +Input: s = ")(" +Output: [""] + + +Constraints: + +1 <= s.length <= 25 +s consists of lowercase English letters and parentheses '(' and ')'. +There will be at most 20 parentheses in s. +""" + +# https://leetcode.com/problems/remove-invalid-parentheses/discuss/75027/Easy-Short-Concise-and-Fast-Java-DFS-3-ms-solution + +class Solution(object): + def removeInvalidParentheses(self, s): + """ + :type s: str + :rtype: List[str] + """ + + ret = [] + def remove(s_to_check, start_from, pointer, left_to_right): + ''' + start_from: start point to check if the char is ')'; it should not be ')' to begin with + pointer: loop through the string `s` + ''' + counter = 0 + + while pointer < len(s_to_check): + if s_to_check[pointer] == '(': + counter += 1 + elif s_to_check[pointer] == ')': + counter -= 1 + + if counter >= 0: + pointer += 1 + continue + + # pointer points to the char of excessive ')' + i = start_from + while i <= pointer: + # should check `i != start_from` instead of `i > 0`: + # the sequence prior to `start_from` has aligned + if s_to_check[i] == ')' and (i == start_from or s_to_check[i - 1] != ')'): + remove(s_to_check[:i] + s_to_check[i+1:], i, pointer, left_to_right) + + i += 1 + return + + mapping = {'(': ')', ')': '('} + reversed_s = ''.join(mapping.get(ch, ch) for ch in reversed(s_to_check)) + if counter > 0: + remove(reversed_s, 0, 0, False) + else: + ret.append(s_to_check) if left_to_right else ret.append(reversed_s) + + remove(s, 0, 0, True) + return ret diff --git a/310_Minimum_Height_Trees.py b/310_Minimum_Height_Trees.py new file mode 100644 index 0000000..e70247b --- /dev/null +++ b/310_Minimum_Height_Trees.py @@ -0,0 +1,166 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +310. Minimum Height Trees + +Created by Shengwei on 2024-03-11. + +Used: +* Meta: https://www.1point3acres.com/bbs/thread-1050777-1-1.html +""" + +# https://leetcode.com/problems/minimum-height-trees/description/ +# tags: medium / hard, tree, minimum, bfs + +""" +A tree is an undirected graph in which any two vertices are connected by exactly one path. In other words, any connected graph without simple cycles is a tree. + +Given a tree of n nodes labelled from 0 to n - 1, and an array of n - 1 edges where edges[i] = [ai, bi] indicates that there is an undirected edge between the two nodes ai and bi in the tree, you can choose any node of the tree as the root. When you select a node x as the root, the result tree has height h. Among all possible rooted trees, those with minimum height (i.e. min(h)) are called minimum height trees (MHTs). + +Return a list of all MHTs' root labels. You can return the answer in any order. + +The height of a rooted tree is the number of edges on the longest downward path between the root and a leaf. + + + +Example 1: + + +Input: n = 4, edges = [[1,0],[1,2],[1,3]] +Output: [1] +Explanation: As shown, the height of the tree is 1 when the root is the node with label 1 which is the only MHT. +Example 2: + + +Input: n = 6, edges = [[3,0],[3,1],[3,2],[3,4],[5,4]] +Output: [3,4] + + +Constraints: + +1 <= n <= 2 * 104 +edges.length == n - 1 +0 <= ai, bi < n +ai != bi +All the pairs (ai, bi) are distinct. +The given input is guaranteed to be a tree and there will be no repeated edges. +""" + +# https://leetcode.com/problems/minimum-height-trees/solutions/1631179/c-python-3-simple-solution-w-explanation-brute-force-2x-dfs-remove-leaves-w-bfs + + +# Accepted +class Solution: + def findMinHeightTrees(self, n: int, edges: List[List[int]]) -> List[int]: + if n == 0: + return [] + if n == 1: + return [0] + + # adjacent matrix + links = defaultdict(set) + for e in edges: + links[e[0]].add(e[1]) + links[e[1]].add(e[0]) + # print('links: {}'.format(links)) + + # remove leaves layer by layer + candidates = [node for node in links if len(links[node]) == 1] + while candidates: + leaves = candidates + candidates = set() + for leaf in leaves: + if links[leaf]: # necessary when there are 2 left + adj = links[leaf].pop() + links[adj].remove(leaf) + if len(links[adj]) == 1: + candidates.add(adj) + + return list(leaves) + + + +# Opetimized sparse 2-d matrix with defaultdict +class Solution: + def findMinHeightTrees(self, n: int, edges: List[List[int]]) -> List[int]: + if n == 0: + return [] + if n == 1: + return [0] + + # adjacent matrix + links = defaultdict(list) + for e in edges: + links[e[0]].append(e[1]) + links[e[1]].append(e[0]) + # print('links: {}'.format(links)) + + # breath-first: calculate lengths from each node to others + buffer, visited = deque([0]), set() + lengths = defaultdict(dict) + while buffer: + current = buffer.popleft() + visited.add(current) + + for neighbor in links[current]: + if neighbor not in visited: + buffer.append(neighbor) + lengths[current][neighbor] = 1 + lengths[neighbor][current] = 1 + else: + for connect, length in lengths[neighbor].items(): + if connect != current and length: + lengths[current][connect] = length + 1 + lengths[connect][current] = length + 1 + # print('lengths: {}'.format(lengths)) + + # max(lengths) is the height of the tree + counts = defaultdict(set) + shortest = n + for node, hs in lengths.items(): + shortest = min(shortest, max(hs.values())) + counts[max(hs.values())].add(node) + # print('counts: {}'.format(counts)) + return list(counts[shortest]) + + +# Initial +class Solution: + def findMinHeightTrees(self, n: int, edges: List[List[int]]) -> List[int]: + if n == 0: + return [] + + # adjacent matrix + links = defaultdict(list) + for e in edges: + links[e[0]].append(e[1]) + links[e[1]].append(e[0]) + # print('links: {}'.format(links)) + + # breath-first: calculate lengths from each node to others + buffer, visited = deque([0]), set() + lengths = [[0] * n for _ in range(n)] + while buffer: + current = buffer.popleft() + visited.add(current) + + for neighbor in links[current]: + if neighbor not in visited: + buffer.append(neighbor) + lengths[current][neighbor] = 1 + lengths[neighbor][current] = 1 + else: + for connect, length in enumerate(lengths[neighbor]): + if connect != current and length: + lengths[current][connect] = length + 1 + lengths[connect][current] = length + 1 + # print('lengths: {}'.format(lengths)) + + # max(lengths) is the height of the tree + counts = defaultdict(set) + shortest = n + for node, hs in enumerate(lengths): + shortest = min(shortest, max(hs)) + counts[max(hs)].add(node) + # print('counts: {}'.format(counts)) + return list(counts[shortest]) diff --git a/329_Longest_Increasing_Path_in_a_Matrix.py b/329_Longest_Increasing_Path_in_a_Matrix.py new file mode 100755 index 0000000..269b8cc --- /dev/null +++ b/329_Longest_Increasing_Path_in_a_Matrix.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +329. Longest Increasing Path in a Matrix + +Created by Shengwei on 2022-04-21. +""" + +# https://leetcode.com/problems/longest-increasing-path-in-a-matrix/ +# tags: medium / hard, recursion, dfs, memory + +""" +Given an m x n integers matrix, return the length of the longest increasing path in matrix. + +From each cell, you can either move in four directions: left, right, up, or down. You may not move diagonally or move outside the boundary (i.e., wrap-around is not allowed). + + +Example 1: + +Input: matrix = [[9,9,4],[6,6,8],[2,1,1]] +Output: 4 +Explanation: The longest increasing path is [1, 2, 6, 9]. + +Example 2: + +Input: matrix = [[3,4,5],[3,2,6],[2,2,1]] +Output: 4 +Explanation: The longest increasing path is [3, 4, 5, 6]. Moving diagonally is not allowed. + +Example 3: + +Input: matrix = [[1]] +Output: 1 + + +Constraints: + +m == matrix.length +n == matrix[i].length +1 <= m, n <= 200 +""" + +class Solution: + def longestIncreasingPath(self, matrix: List[List[int]]) -> int: + if not matrix or not matrix[0]: + return 0 + + m, n = len(matrix), len(matrix[0]) + length_matrix = [[0] * n for _ in range(m)] + dirs = ((1, 0), (-1, 0), (0, -1), (0, 1)) + + def get_length(i, j): + max_distance = 1 + for d in dirs: + ai, aj = i + d[0], j + d[1] + if ai < 0 or ai >= m or aj < 0 or aj >= n: # note: '>=' size + continue + + if matrix[ai][aj] > matrix[i][j]: + if length_matrix[ai][aj] == 0: + get_length(ai, aj) + max_distance = max(max_distance, 1 + length_matrix[ai][aj]) + + length_matrix[i][j] = max_distance + + for i in range(m): + for j in range(n): + get_length(i, j) + + # print(length_matrix) + return max(max(row) for row in length_matrix) diff --git a/410_Split_Array_Largest_Sum.py b/410_Split_Array_Largest_Sum.py new file mode 100644 index 0000000..709139a --- /dev/null +++ b/410_Split_Array_Largest_Sum.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +410. Split Array Largest Sum + +Created by Shengwei on 2023-10-28. +""" + +# https://leetcode.com/problems/split-array-largest-sum/description/ +# tags: hard, array, number, binary + +""" +Given an integer array nums and an integer k, split nums into k non-empty subarrays such that the largest sum of any subarray is minimized. + +Return the minimized largest sum of the split. + +A subarray is a contiguous part of the array. + + + +Example 1: + +Input: nums = [7,2,5,10,8], k = 2 +Output: 18 +Explanation: There are four ways to split nums into two subarrays. +The best way is to split it into [7,2,5] and [10,8], where the largest sum among the two subarrays is only 18. +Example 2: + +Input: nums = [1,2,3,4,5], k = 2 +Output: 9 +Explanation: There are four ways to split nums into two subarrays. +The best way is to split it into [1,2,3] and [4,5], where the largest sum among the two subarrays is only 9. + + +Constraints: + +1 <= nums.length <= 1000 +0 <= nums[i] <= 106 +1 <= k <= min(50, nums.length) +""" + +# greedy: go from left to right and sum up to a limit --> results in the least number of splits + +class Solution: + def splitArray(self, nums: List[int], k: int) -> int: + start, end = max(nums), sum(nums) + while start < end: + mid = (start + end) // 2 + + # note: count should start with 1 instead of 0, + # or +1 after the loop to count for the last split + count, total = 1, 0 + for num in nums: + total += num + if total > mid: + count += 1 + total = num + + # note: should not return just yet + # if count == k: + # return mid + + if count > k: + start = mid + 1 + else: + end = mid + + # note: "end" is either sum(nums) or some value that + # has been validated as "mid" + return end diff --git a/528_Random_Pick_with_Weight.py b/528_Random_Pick_with_Weight.py new file mode 100644 index 0000000..e51cb53 --- /dev/null +++ b/528_Random_Pick_with_Weight.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +528. Random Pick with Weight + +Created by Shengwei on 2023-11-05. + +Used: +* Meta: https://www.1point3acres.com/bbs/thread-1026011-1-1.html +""" + +# https://leetcode.com/problems/random-pick-with-weight/description/ +# tags: medium, array, random, probability, binary + +""" +You are given a 0-indexed array of positive integers w where w[i] describes the weight of the ith index. + +You need to implement the function pickIndex(), which randomly picks an index in the range [0, w.length - 1] (inclusive) and returns it. The probability of picking an index i is w[i] / sum(w). + +For example, if w = [1, 3], the probability of picking index 0 is 1 / (1 + 3) = 0.25 (i.e., 25%), and the probability of picking index 1 is 3 / (1 + 3) = 0.75 (i.e., 75%). + + +Example 1: + +Input +["Solution","pickIndex"] +[[[1]],[]] +Output +[null,0] + +Explanation +Solution solution = new Solution([1]); +solution.pickIndex(); // return 0. The only option is to return 0 since there is only one element in w. +Example 2: + +Input +["Solution","pickIndex","pickIndex","pickIndex","pickIndex","pickIndex"] +[[[1,3]],[],[],[],[],[]] +Output +[null,1,1,1,1,0] + +Explanation +Solution solution = new Solution([1, 3]); +solution.pickIndex(); // return 1. It is returning the second element (index = 1) that has a probability of 3/4. +solution.pickIndex(); // return 1 +solution.pickIndex(); // return 1 +solution.pickIndex(); // return 1 +solution.pickIndex(); // return 0. It is returning the first element (index = 0) that has a probability of 1/4. + +Since this is a randomization problem, multiple answers are allowed. +All of the following outputs can be considered correct: +[null,1,1,1,1,0] +[null,1,1,1,1,1] +[null,1,1,1,0,0] +[null,1,1,1,0,1] +[null,1,0,1,0,0] +...... +and so on. + + +Constraints: + +1 <= w.length <= 104 +1 <= w[i] <= 105 +pickIndex will be called at most 104 times. +""" + + +# 20231105 + +#Inspired by https://leetcode.com/problems/random-pick-with-weight/solutions/154044/java-accumulated-freq-sum-binary-search/ +class Solution: + + def __init__(self, w: List[int]): + self.slots = w.copy() + for index in range(1, len(w)): + self.slots[index] += self.slots[index - 1] + + def pickIndex(self) -> int: + import random + pick = random.randrange(self.slots[-1]) + 1 + left, right = 0, len(self.slots) - 1 + while left < right: + mid = (left + right) // 2 + mid_slot = self.slots[mid] + if mid_slot == pick: + return mid + if mid_slot < pick: + left = mid + 1 + else: + right = mid + + return left + + +# Memory Limit Exceeded +class Solution: + + def __init__(self, w: List[int]): + self.array = [] + for i, wi in enumerate(w): + self.array += [i] * wi + self.size = len(self.array) + + def pickIndex(self) -> int: + import random + return self.array[random.randrange(self.size)] + + +# 20220430 +class Solution: + + def __init__(self, w: List[int]): + self.slots = [0] + for num in w: + self.slots.append(self.slots[-1] + num) + # print(self.slots) + + def pickIndex(self) -> int: + import random + pick = random.randrange(self.slots[-1]) + 1 + # print(pick) + pos = self._search(pick) + return pos + + def _search(self, pick: int) -> int: + left, right = 0, len(self.slots) + while left < right: + mid = (left + right) // 2 + mid_slot = self.slots[mid] + if mid_slot == pick: + return mid + + if mid_slot > pick: + right = mid + else: + left = mid + 1 + + return left + + +# Your Solution object will be instantiated and called as such: +# obj = Solution(w) +# param_1 = obj.pickIndex() diff --git a/543_Diameter_of_Binary_Tree.py b/543_Diameter_of_Binary_Tree.py new file mode 100644 index 0000000..b54e16e --- /dev/null +++ b/543_Diameter_of_Binary_Tree.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +543. Diameter of Binary Tree + +Created by Shengwei on 2023-10-06. + +Used: +* Meta: https://www.1point3acres.com/bbs/thread-1026007-1-1.html +""" + +# https://leetcode.com/problems/diameter-of-binary-tree/description/ +# tags: easy / medium, tree, recursion + +""" +Given the root of a binary tree, return the length of the diameter of the tree. + +The diameter of a binary tree is the length of the longest path between any two nodes in a tree. This path may or may not pass through the root. + +The length of a path between two nodes is represented by the number of edges between them. + + + +Example 1: + + +Input: root = [1,2,3,4,5] +Output: 3 +Explanation: 3 is the length of the path [4,2,1,3] or [5,2,1,3]. +Example 2: + +Input: root = [1,2] +Output: 1 + + +Constraints: + +The number of nodes in the tree is in the range [1, 104]. +-100 <= Node.val <= 100 +""" + +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, val=0, left=None, right=None): +# self.val = val +# self.left = left +# self.right = right + + +class Solution: + def diameterOfBinaryTree(self, root: Optional[TreeNode]) -> int: + + def check(node): + if not node: + return 0, 0 + + left_length, left_diameter = check(node.left) + right_length, right_diameter = check(node.right) + current_diameter = left_length + right_length + + return (max(left_length, right_length) + 1, + max(left_diameter, right_diameter, current_diameter)) + + return check(root)[1] + + +class Solution: + def diameterOfBinaryTree(self, root: Optional[TreeNode]) -> int: + + def check(node): + if not node.left and not node.right: + return 1, 0 + + if node.left: + left_length, left_diameter = check(node.left) + else: + left_length = left_diameter = 0 + + if node.right: + right_length, right_diameter = check(node.right) + else: + right_length = right_diameter = 0 + + current_diameter = left_length + right_length + + return (max(left_length, right_length) + 1, + max(left_diameter, right_diameter, current_diameter)) + + return check(root)[1] \ No newline at end of file diff --git a/680_Valid_Palindrome_II.py b/680_Valid_Palindrome_II.py new file mode 100755 index 0000000..db9d941 --- /dev/null +++ b/680_Valid_Palindrome_II.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +680. Valid Palindrome II + +Created by Shengwei on 2022-04-18. +""" + +# https://leetcode.com/problems/valid-palindrome-ii/ +# tags: easy / medium, string + +""" +Given a string s, return true if the s can be palindrome after deleting at most one character from it. + +Example 1: + +Input: s = "aba" +Output: true + +Example 2: + +Input: s = "abca" +Output: true +Explanation: You could delete the character 'c'. + +Example 3: + +Input: s = "abc" +Output: false + + +Constraints: + +1 <= s.length <= 105 +s consists of lowercase English letters. +""" + +class Solution: + def validPalindrome(self, s: str) -> bool: + if not s: return True + + left, right = 0, len(s) - 1 + while left < right: + if s[left] != s[right]: + break + left += 1 + right -= 1 + + passed = True + l, r = left + 1, right + while l < r: + if s[l] != s[r]: + passed = False + break + l += 1 + r -= 1 + + if passed: return True + l, r = left, right - 1 + while l < r: + if s[l] != s[r]: + return False + l += 1 + r -= 1 + + return True + + +class Solution: + def validPalindrome(self, s: str) -> bool: + if not s: return True + + def is_pal(s): + return s == s[::-1] + + left, right = 0, len(s) - 1 + while left < right: + if s[left] != s[right]: + break + left += 1 + right -= 1 + + return is_pal(s[left+1:right+1]) or is_pal(s[left:right]) diff --git a/921_Minimum_Add_to_Make_Parentheses_Valid.py b/921_Minimum_Add_to_Make_Parentheses_Valid.py new file mode 100644 index 0000000..8241993 --- /dev/null +++ b/921_Minimum_Add_to_Make_Parentheses_Valid.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +921. Minimum Add to Make Parentheses Valid + +Created by Shengwei on 2023-11-06. + +Used: +* Meta: https://www.1point3acres.com/bbs/thread-1026007-1-1.html +""" + +# https://leetcode.com/problems/minimum-add-to-make-parentheses-valid/description/ +# tags: easy, parentheses, logic + +""" +A parentheses string is valid if and only if: + +It is the empty string, +It can be written as AB (A concatenated with B), where A and B are valid strings, or +It can be written as (A), where A is a valid string. +You are given a parentheses string s. In one move, you can insert a parenthesis at any position of the string. + +For example, if s = "()))", you can insert an opening parenthesis to be "(()))" or a closing parenthesis to be "())))". +Return the minimum number of moves required to make s valid. + + + +Example 1: + +Input: s = "())" +Output: 1 +Example 2: + +Input: s = "(((" +Output: 3 + + +Constraints: + +1 <= s.length <= 1000 +s[i] is either '(' or ')'. +""" + +class Solution: + def minAddToMakeValid(self, s: str) -> int: + lc = rc = 0 + for c in s: + if c == '(': + lc += 1 + elif c == ')': + if lc: + lc -= 1 + else: + rc += 1 + return lc + rc diff --git a/973_K_Closest_Points_to_Origin.py b/973_K_Closest_Points_to_Origin.py new file mode 100755 index 0000000..9c1917a --- /dev/null +++ b/973_K_Closest_Points_to_Origin.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +973. K Closest Points to Origin + +Created by Shengwei on 2022-04-21. +""" + +# https://leetcode.com/problems/k-closest-points-to-origin/ +# tags: medium, quicksort + +""" +Given an array of points where points[i] = [xi, yi] represents a point on the X-Y plane and an integer k, return the k closest points to the origin (0, 0). + +The distance between two points on the X-Y plane is the Euclidean distance (i.e., √(x1 - x2)2 + (y1 - y2)2). + +You may return the answer in any order. The answer is guaranteed to be unique (except for the order that it is in). + + +Example 1: + +Input: points = [[1,3],[-2,2]], k = 1 +Output: [[-2,2]] +Explanation: +The distance between (1, 3) and the origin is sqrt(10). +The distance between (-2, 2) and the origin is sqrt(8). +Since sqrt(8) < sqrt(10), (-2, 2) is closer to the origin. +We only want the closest k = 1 points from the origin, so the answer is just [[-2,2]]. + + +Example 2: + +Input: points = [[3,3],[5,-1],[-2,4]], k = 2 +Output: [[3,3],[-2,4]] +Explanation: The answer [[-2,4],[3,3]] would also be accepted. +""" + +class Solution: + def kClosest(self, points: List[List[int]], K: int) -> List[List[int]]: + if K >= len(points): + return points + + array = [(x * x + y * y, x, y) for x, y in points] + def qsort(l: int, r: int) -> int: + pivot = array[l] + while l < r: + while array[r][0] >= pivot[0] and l < r: + r -= 1 + array[l] = array[r] + while array[l][0] <= pivot[0] and l < r: + l += 1 + array[r] = array[l] + array[l] = pivot + return l + + left, right = 0, len(array) - 1 + while left < K: + mid = qsort(left, right) + if K > mid: + left = mid + 1 + else: + right = mid - 1 + return [[x, y] for _, x, y in array[:K]] From ba5c654e9c7bfff72e5d1b78d6390211b5c049ee Mon Sep 17 00:00:00 2001 From: Shengwei Li Date: Mon, 15 Apr 2024 17:34:03 -0400 Subject: [PATCH 05/16] went through first 150 problems once --- ....py => 124_binary_tree_maximum_path_sum.py | 26 ++++++ ....py => 128_longest_consecutive_sequence.py | 2 +- 437_Path_Sum_III.py | 81 ++++++++++++++++++ 4sum.py | 8 +- binary_tree_inorder.py | 8 ++ binary_tree_level_order_traversal.py | 2 +- binary_tree_postorder.py | 2 + binary_tree_preorder.py | 13 +++ climb_stairs.py | 14 ++++ combination_sum.py | 2 +- combination_sum_ii.py | 2 +- combinations.py | 3 +- decode_ways.py | 4 +- first150/11_container_with_most_water.py | 2 +- first150/5_longest_palindrome_substring.py | 1 + generate_parentheses.py | 15 ++++ gray_code.py | 31 +++++++ insert_intervals.py | 83 +++++++++++++++++++ interleave_string.py | 74 ++++++++++++++++- longest_common_prefix.py | 4 + longest_valid_parentheses.py | 1 + minimal_window_string.py | 38 +++++++++ n_queens.py | 4 +- next_permutation.py | 7 ++ ...tition_ii.py => palindrome_partition_ii.py | 25 ++++++ path_sum_ii.py | 3 +- permutation_sequence.py | 1 + permutations.py | 43 +++++++--- plus_one.py | 1 + recover_binary_search_tree.py | 2 +- restore_ip_address.py | 6 +- scramble_string.py | 4 +- spiral_matrix.py | 5 +- strStr.py | 44 +++++++++- subsets.py | 21 ++++- unique_path.py | 2 +- validate_binary_search_tree.py | 3 +- 37 files changed, 547 insertions(+), 40 deletions(-) rename binary_tree_maximum_path_sum.py => 124_binary_tree_maximum_path_sum.py (84%) rename longest_consecutive_sequence.py => 128_longest_consecutive_sequence.py (95%) create mode 100644 437_Path_Sum_III.py rename palindrome_patition_ii.py => palindrome_partition_ii.py (76%) diff --git a/binary_tree_maximum_path_sum.py b/124_binary_tree_maximum_path_sum.py similarity index 84% rename from binary_tree_maximum_path_sum.py rename to 124_binary_tree_maximum_path_sum.py index e402580..f1a90d2 100755 --- a/binary_tree_maximum_path_sum.py +++ b/124_binary_tree_maximum_path_sum.py @@ -34,6 +34,32 @@ # self.left = None # self.right = None + +# 20240330 +class Solution: + + def __init__(self): + self.cur_max = 0 + + def max_sum(self, node): + if not node: + return 0 + + # print('current', node.value) + left_sum = self.max_sum(node.left) + # print('left', left_sum) + right_sum = self.max_sum(node.right) + # print('right', right_sum) + self.cur_max = max(cur_max, left_sum + right_sum + node.value) + # print('cur_max', self.cur_max) + return max(0, left_sum + node.value, right_sum + node.value) + + def maxPathSum(self, root): + self.max_sum(root) + return self.cur_max + + +# 2014 class Solution: # @param root, a tree node # @return an integer diff --git a/longest_consecutive_sequence.py b/128_longest_consecutive_sequence.py similarity index 95% rename from longest_consecutive_sequence.py rename to 128_longest_consecutive_sequence.py index efdcfe2..f7c7f1b 100755 --- a/longest_consecutive_sequence.py +++ b/128_longest_consecutive_sequence.py @@ -7,7 +7,7 @@ """ # https://oj.leetcode.com/problems/longest-consecutive-sequence/ -# tags: hard, array, numbers, hashtable, longest +# tags: hard, array, numbers, hashtable, longest, tricky """ Given an unsorted array of integers, find the length of the longest consecutive elements sequence. diff --git a/437_Path_Sum_III.py b/437_Path_Sum_III.py new file mode 100644 index 0000000..3219782 --- /dev/null +++ b/437_Path_Sum_III.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +437. Path Sum III + +Created by Shengwei on 2024-04-13. +""" + +# https://leetcode.com/problems/path-sum-iii/description/ +# tags: medium / hard, tree, sum, dfs + +""" +Given the root of a binary tree and an integer targetSum, return the number of paths where the sum of the values along the path equals targetSum. + +The path does not need to start or end at the root or a leaf, but it must go downwards (i.e., traveling only from parent nodes to child nodes). + + + +Example 1: + + +Input: root = [10,5,-3,3,2,null,11,3,-2,null,1], targetSum = 8 +Output: 3 +Explanation: The paths that sum to 8 are shown. +Example 2: + +Input: root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22 +Output: 3 + + +Constraints: + +The number of nodes in the tree is in the range [0, 1000]. +-109 <= Node.val <= 109 +-1000 <= targetSum <= 1000 +""" + +# https://leetcode.com/problems/path-sum-iii/solutions/141424/python-step-by-step-walk-through-easy-to-understand-two-solutions-comparison/ +# TODO: use cache to reverse the search for full targetSum, putting 1 in cache ahead to pick up later + + + + +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, val=0, left=None, right=None): +# self.val = val +# self.left = left +# self.right = right +class Solution: + def pathSum(self, root: Optional[TreeNode], targetSum: int) -> int: + + @cache + def sub(node, target): + if not node: + return 0 + + count = dfs(node, target) + count += sub(node.left, targetSum) + # print('left full:', node.val, count) + count += sub(node.right, targetSum) + # print('right full:', node.val, count) + + # print(node.val, target, count, list(n.val for n in processed if n)) + return count + + def dfs(node, target): + """dfs search must separate from `sub` so full targetSum search only happens once + """ + if not node: + return 0 + + count = dfs(node.left, target - node.val) + # print('left res:', node.val, count) + count += dfs(node.right, target - node.val) + # print('right res:', node.val, count) + if node.val == target: + count += 1 + return count + + return sub(root, targetSum) \ No newline at end of file diff --git a/4sum.py b/4sum.py index 4499ac7..dca130d 100755 --- a/4sum.py +++ b/4sum.py @@ -31,8 +31,8 @@ class Solution: # @return a list of lists of length 4, [[val1,val2,val3,val4]] def fourSum(self, nums, target): - nums = sorted(nums) - result = set() + nums = sorted(nums) # sorted list guarantee unique results; otherwise, dedup also works + results = set() # cache all the seen sub-sum of any two numbers prior to current cursor cache = collections.defaultdict(set) @@ -40,13 +40,13 @@ def fourSum(self, nums, target): # find supplimentray sub-sum in cache for the sub-sum of current cursor and any number after it for j in range(i + 1, len(nums)): for half in cache[target - nums[i] - nums[j]]: - result.add(tuple(list(half) + [nums[i], nums[j]])) + results.add(tuple(list(half) + [nums[i], nums[j]])) # cache the sub-sum of current cursor and any number prior to it for k in range(i): cache[nums[i] + nums[k]].add((nums[k], nums[i])) - return map(list, result) + return map(list, results) ############ without two sum but using `set` (faster) ############ diff --git a/binary_tree_inorder.py b/binary_tree_inorder.py index 179f0ef..5a3a41b 100755 --- a/binary_tree_inorder.py +++ b/binary_tree_inorder.py @@ -38,6 +38,14 @@ # 20231029 def inorder(root): + """logic: + 1. put node to stack when it's not None and move to the left + - this cannot be: put node's left to the stack when its not None; + it will remove the left child and then put it back when visiting its parent later + - alternative: add the left child regardless of its None or not; + see the next solution below for adaption to that approach + 2. process the top of stack if it has no left, then put the right in stack + """ cur = root stack = [] while stack or cur: diff --git a/binary_tree_level_order_traversal.py b/binary_tree_level_order_traversal.py index 2433310..2d1fff6 100755 --- a/binary_tree_level_order_traversal.py +++ b/binary_tree_level_order_traversal.py @@ -7,7 +7,7 @@ """ # https://oj.leetcode.com/problems/binary-tree-level-order-traversal/ -# tags: easy, tree, traversal, bfs +# tags: easy, tree, traversal, bfs, generator """ Given a binary tree, return the level order traversal of its nodes' values. (ie, from left to right, level by level). diff --git a/binary_tree_postorder.py b/binary_tree_postorder.py index 4a8f742..a0ed123 100755 --- a/binary_tree_postorder.py +++ b/binary_tree_postorder.py @@ -24,6 +24,8 @@ Note: Recursive solution is trivial, could you do it iteratively? """ +# See also: postorder iterator - https://gist.github.com/senvey/4d4f7192b3718adf9d8c + ##### V3 ##### # Definition for a binary tree node diff --git a/binary_tree_preorder.py b/binary_tree_preorder.py index 35128a8..c5a8880 100755 --- a/binary_tree_preorder.py +++ b/binary_tree_preorder.py @@ -31,6 +31,19 @@ # self.left = None # self.right = None + +# 20240329 +def preorder(root): + cur, stack = root, [] + while stack or cur: + if cur: + print(cur.value) + stack.append(cur) + cur = cur.left + else: + cur = stack.pop().right + + class Solution: # @param root, a tree node # @return a list of integers diff --git a/climb_stairs.py b/climb_stairs.py index ed5e77e..8c1b726 100755 --- a/climb_stairs.py +++ b/climb_stairs.py @@ -17,6 +17,20 @@ # TODO: different ways to do this + + +# 20240329 +def climb(n): + if n <= 2: + return n + + prior, cur = 1, 2 + for _ in range(n - 2): + prior, cur = cur, prior + cur + return cur + + + class Solution: # @param n, an integer # @return an integer diff --git a/combination_sum.py b/combination_sum.py index fb29977..13b4408 100755 --- a/combination_sum.py +++ b/combination_sum.py @@ -7,7 +7,7 @@ """ # https://oj.leetcode.com/problems/combination-sum/ -# tags: medium, array, combination, sum, dp, recursion, dfs +# tags: medium, array, combination, sum, dp, recursion, dfs, generator """ Given a set of candidate numbers (C) and a target number (T), find all unique combinations in C where the candidate numbers sums to T. diff --git a/combination_sum_ii.py b/combination_sum_ii.py index edf6765..58db315 100755 --- a/combination_sum_ii.py +++ b/combination_sum_ii.py @@ -7,7 +7,7 @@ """ # https://oj.leetcode.com/problems/combination-sum-ii/ -# tags: medium, array, combination, sum, dp, recursion, dfs +# tags: medium, array, combination, sum, dp, recursion, dfs, generator """ Given a collection of candidate numbers (C) and a target number (T), find all unique combinations in C where the candidate numbers sums to T. diff --git a/combinations.py b/combinations.py index d7da127..3d7f191 100755 --- a/combinations.py +++ b/combinations.py @@ -72,7 +72,6 @@ def comb(array, k): # 04/13/2022 def combine(n, k): - array = range(1, n + 1) res = set() def comb(start, sub_arr): @@ -81,7 +80,7 @@ def comb(start, sub_arr): return for i in range(start, n): - sub_arr.append(array[i]) + sub_arr.append(i + 1) comb(i + 1, sub_arr) sub_arr.pop() diff --git a/decode_ways.py b/decode_ways.py index e1eeed8..d7119db 100755 --- a/decode_ways.py +++ b/decode_ways.py @@ -63,7 +63,7 @@ def numDecodings(self, s): if int(s[0]) != 0: if s[1:] == '': return 1 ways += self.numDecodings(s[1:]) - if int(s[:2]) > 9 and int(s[:2]) < 27: - if s[:2] == '': return ways + 1 + if 9 < int(s[:2]) < 27: + if s[2:] == '': return ways + 1 ways += self.numDecodings(s[2:]) return ways diff --git a/first150/11_container_with_most_water.py b/first150/11_container_with_most_water.py index e1090f1..b081a89 100755 --- a/first150/11_container_with_most_water.py +++ b/first150/11_container_with_most_water.py @@ -7,7 +7,7 @@ """ # https://oj.leetcode.com/problems/container-with-most-water/ -# tags: medium / hard, arrays, water, greedy, logic, shortcut +# tags: medium / hard, arrays, water, greedy, logic, shortcut, tricky """ Given n non-negative integers a1, a2, ..., an, where each represents a point at coordinate (i, ai). n vertical lines are drawn such that the two endpoints of line i is at (i, ai) and (i, 0). Find two lines, which together with x-axis forms a container, such that the container contains the most water. diff --git a/first150/5_longest_palindrome_substring.py b/first150/5_longest_palindrome_substring.py index 6830637..93369de 100755 --- a/first150/5_longest_palindrome_substring.py +++ b/first150/5_longest_palindrome_substring.py @@ -14,6 +14,7 @@ """ ############# O(n) ############# +# https://hackernoon.com/manachers-algorithm-explained-longest-palindromic-substring-22cb27a5e96f # TODO diff --git a/generate_parentheses.py b/generate_parentheses.py index 944614d..35b8227 100755 --- a/generate_parentheses.py +++ b/generate_parentheses.py @@ -21,6 +21,21 @@ # DP: https://leetcode.com/problems/generate-parentheses/solutions/10369/clean-python-dp-solution/ +# 20240330 +def generate(count): + res = set() + def routine(s, left, right): + if right == 0: + res.add(s.decode()) + return + if left > 0: + routine(s + b'(', left - 1, right) + if right > left: + routine(s + b')', left, right - 1) + routine(bytearray('', 'utf-8'), count, count) + return res + + # 20231104 def generate(count): res = [] diff --git a/gray_code.py b/gray_code.py index 8d257af..4827c0c 100755 --- a/gray_code.py +++ b/gray_code.py @@ -28,15 +28,46 @@ For now, the judge is able to judge based on one instance of gray code sequence. Sorry about that. """ + +# TODO: iterative way + + +# 20240330 +class Solution: + def grayCode(self, n: int) -> List[int]: + if n == 0: + return [0] + + sub = self.grayCode(n - 1) + + # effectively add 0 to the right + ext_0 = [x << 1 for x in sub] + + # effectively add 1 to the right + ext_1 = [(x << 1) + 1 for x in sub] + + return ext_0 + list(reversed(ext_1)) + + class Solution: # @return a list of integers def grayCode(self, n): + """ grayCode(2) -> [00, 01, 11, 10] + grayCode(3): + - add 0 to the left of gc(2) -> [000, 001, 011, 010] + - add 1 to the left of reversed gc(2) -> [110, 111, 101, 100] + - piece them together: [000, 001, 011, 010, 110, 111, 101, 100] + """ if n == 0: return [0] sub_code = self.grayCode(n - 1) + # the following is the same as: + # reversed_code = [code | 1 << n - 1 for code in reversed(sub_code)] reversed_code = [] for code in reversed(sub_code): + # sign priority: -, <<, | + # essentially: code | (1 << (n - 1)) reversed_code.append(code | 1 << n - 1) return sub_code + reversed_code diff --git a/insert_intervals.py b/insert_intervals.py index f420e7b..377e77c 100755 --- a/insert_intervals.py +++ b/insert_intervals.py @@ -23,6 +23,89 @@ This is because the new interval [4,9] overlaps with [3,5],[6,7],[8,10]. """ + +# 20240411 +class Solution: + def insert(self, intervals: List[List[int]], newInterval: List[int]) -> List[List[int]]: + if not intervals: + return [newInterval] + if not newInterval: + return intervals + + # note: edge cases if new interval is greater or smaller than all existing ones + if newInterval[1] < intervals[0][0]: + return [newInterval] + intervals + if newInterval[0] > intervals[-1][1]: + return intervals + [newInterval] + + begins, ends = [], [] + begin = end = None + for interval in intervals: + if interval[1] < newInterval[0]: + begins.append(interval[0]) + ends.append(interval[1]) + elif interval[0] > newInterval[1]: + if newInterval[0] > ends[-1]: + # note: edge case when there is no overlap for new interval + begins.append(newInterval[0]) + ends.append(newInterval[1]) + begins.append(interval[0]) + ends.append(interval[1]) + else: + if begin is None: + begin = min(interval[0], newInterval[0]) + begins.append(begin) + else: + begins[-1] = min(begins[-1], interval[0]) + + if end is None: + end = max(interval[1], newInterval[1]) + ends.append(end) + else: + ends[-1] = max(ends[-1], interval[1]) + + return list(zip(begins, ends)) + +# alternative / reversed if..else +class Solution: + def insert(self, intervals: List[List[int]], newInterval: List[int]) -> List[List[int]]: + if not intervals: + return [newInterval] + if not newInterval: + return intervals + + if newInterval[1] < intervals[0][0]: + return [newInterval] + intervals + if newInterval[0] > intervals[-1][1]: + return intervals + [newInterval] + + begins, ends = [], [] + begin = end = None + for interval in intervals: + if interval[1] >= newInterval[0] and interval[0] <= newInterval[1]: + if begin is None: + begin = min(interval[0], newInterval[0]) + begins.append(begin) + else: + begins[-1] = min(begins[-1], interval[0]) + + if end is None: + end = max(interval[1], newInterval[1]) + ends.append(end) + else: + ends[-1] = max(ends[-1], interval[1]) + else: + if interval[0] > newInterval[1] and newInterval[0] > ends[-1]: + begins.append(newInterval[0]) + ends.append(newInterval[1]) + begins.append(interval[0]) + ends.append(interval[1]) + + return list(zip(begins, ends)) + + + + # https://gist.github.com/senvey/772e0afd345934cee475 # note: sorting isn't actually required diff --git a/interleave_string.py b/interleave_string.py index 2053436..30c5dfe 100755 --- a/interleave_string.py +++ b/interleave_string.py @@ -7,7 +7,7 @@ """ # https://oj.leetcode.com/problems/interleaving-string/ -# tags: hard, string, stack, dp +# tags: medium / hard, string, stack, dp """ Given s1, s2, s3, find whether s3 is formed by the interleaving of s1 and s2. @@ -21,6 +21,78 @@ When s3 = "aadbbbaccc", return false. """ + +# 20190926 +class Solution: + def isInterleave(self, s1: str, s2: str, s3: str) -> bool: + cache = {} + + def check(i1: int, i2: int, i3: int) -> bool: + if (i1, i2, i3) in cache: + return cache[(i1, i2, i3)] + + if i1 == len(s1) and i2 == len(s2) and i3 == len(s3): + matched = True + else: + matched = False + + if not matched and i1 < len(s1) and i3 < len(s3) and s1[i1] == s3[i3]: + matched = check(i1 + 1, i2, i3 + 1) + + if not matched and i2 < len(s2) and i3 < len(s3) and s2[i2] == s3[i3]: + matched = check(i1, i2 + 1, i3 + 1) + + cache[(i1, i2, i3)] = matched + return matched + + return check(0, 0, 0) + + +# 20180923 +class Solution(object): + + def isInterleave(self, s1, s2, s3): + """ + :type s1: str + :type s2: str + :type s3: str + :rtype: bool + """ + + cache = {} + + def check(index1, index2, index3): + if index1 == len(s1): + return s2[index2:] == s3[index3:] + if index2 == len(s2): + return s1[index1:] == s3[index3:] + if index3 == len(s3): + return not s1[index1:] and not s2[index2:] + + if s1[index1] != s3[index3] and s2[index2] != s3[index3]: + return False + + is_interleved1 = is_interleved2 = False + if s1[index1] == s3[index3]: + key = (index1 + 1, index2, index3 + 1) + if key in cache: + is_interleved1 = cache[key] + else: + is_interleved1 = check(*key) + cache[key] = is_interleved1 + if s2[index2] == s3[index3]: + key = (index1, index2 + 1, index3 + 1) + if key in cache: + is_interleved2 = cache[key] + else: + is_interleved2 = check(*key) + cache[key] = is_interleved2 + + return is_interleved1 or is_interleved2 + + return check(0, 0, 0) + + # TODO: try to use memorized DP class Solution: diff --git a/longest_common_prefix.py b/longest_common_prefix.py index 79656a2..7786d5b 100755 --- a/longest_common_prefix.py +++ b/longest_common_prefix.py @@ -13,6 +13,10 @@ Write a function to find the longest common prefix string amongst an array of strings. """ + +# easier and faster: https://leetcode.com/problems/longest-common-prefix/solutions/3174307/well-explained-code-using-strings-in-java/ + + class Solution: # @return a string def longestCommonPrefix(self, strs): diff --git a/longest_valid_parentheses.py b/longest_valid_parentheses.py index 66d8afb..0579def 100755 --- a/longest_valid_parentheses.py +++ b/longest_valid_parentheses.py @@ -29,6 +29,7 @@ def longestValidParentheses(self, s): # the last one length[len(s)] is a sentinel lengths = [0] * (len(s) + 1) + # alternative: `for i, chr in enumerate(s):` for i in xrange(len(s)): if s[i] == '(': stack.append(i) diff --git a/minimal_window_string.py b/minimal_window_string.py index ea4b119..45974ee 100755 --- a/minimal_window_string.py +++ b/minimal_window_string.py @@ -26,6 +26,44 @@ # https://leetcode.com/problems/minimum-window-substring/discuss/26804/12-lines-Python +# 20240412 +class Solution: + def minWindow(self, s: str, t: str) -> str: + """Valid example: + s -> 'ABBDCA', t -> 'ABC' + """ + if len(s) < len(t): + return '' + + from collections import Counter + counter = Counter(t) + size = len(t) + + # use `end` as a flag; it will be a valid index if moved + begin, end = 0, len(s) # define current min window + left = right = 0 # define sliding window + + while right < len(s): + if s[right] in counter: + counter[s[right]] -= 1 + if counter[s[right]] >= 0: + size -= 1 + + while size == 0: + if s[left] in counter: + counter[s[left]] += 1 + if counter[s[left]] > 0: + size += 1 + if end - begin > right - left: + begin, end = left, right + left += 1 + + right += 1 + + return s[begin:end+1] if end < len(s) else '' + + + """ Notes for 3 bugs while implementing: 1. should move left pointer when substring is set other than left > 0; diff --git a/n_queens.py b/n_queens.py index 32caddb..639f27b 100755 --- a/n_queens.py +++ b/n_queens.py @@ -7,7 +7,7 @@ """ # https://oj.leetcode.com/problems/n-queens/ -# tags: medim / hard, matrix, bit manipulation, generator, dfs, edge cases, bitmap +# tags: medim / hard, matrix, bit manipulation, generator, dfs, edge cases, bitmap, tricky """ The n-queens puzzle is the problem of placing n queens on an n×n chessboard such that no two queens attack each other. @@ -55,7 +55,7 @@ def search(board, depth, vertical_taken, left_taken, right_taken): while availables: pos = availables & (-availables) # get the rightmost bit that is 1 availables -= pos # remove current pos from availables - index = int(math.log(pos, 2)) # comput the index where to put queen + index = int(math.log(pos, 2)) # compute the index where to put queen # note: remember the recursive call is an iterater and yield again line[index] = 'Q' diff --git a/next_permutation.py b/next_permutation.py index a38037a..a5d0206 100755 --- a/next_permutation.py +++ b/next_permutation.py @@ -32,6 +32,13 @@ class Solution: # @return a list of integer def nextPermutation(self, nums): + # implementation: + # 1. for each num from right to left, reverse them as long as it's increasing + # - compare each new num (from right to left) to the rightmost one + # - keep moving the num to the rightmost to reverse numbers + # 2. from left to right, find the first num that's greater than the decreasing num + # and switch them + # issue: slow on reverse in the #1 step for index in xrange(-1, -len(nums) - 1, -1): if nums[index] < nums[-1]: for switch in xrange(index + 1, 0): diff --git a/palindrome_patition_ii.py b/palindrome_partition_ii.py similarity index 76% rename from palindrome_patition_ii.py rename to palindrome_partition_ii.py index 4ea9942..a2f6ce2 100755 --- a/palindrome_patition_ii.py +++ b/palindrome_partition_ii.py @@ -18,6 +18,27 @@ Return 1 since the palindrome partitioning ["aa","b"] could be produced using 1 cut. """ +# use built-in @cache: https://leetcode.com/problems/palindrome-partitioning-ii/solutions/1388628/python-simple-top-down-dp-clean-concise/ + +# 20240413 -- used sentinel +class Solution: + def minCut(self, s: str) -> int: + cuts = [-1] + palindromes = set() + + for index in range(len(s)): + min_cut = index + for cursor in range(index + 1): + if s[cursor] == s[index] and (index - cursor <= 2 or + (cursor + 1, index - 1) in palindromes): + palindromes.add((cursor, index)) + min_cut = min(min_cut, cuts[cursor] + 1) + + cuts.append(min_cut) + + return cuts[-1] + + # https://oj.leetcode.com/discuss/6691/my-dp-solution-explanation-and-code # https://oj.leetcode.com/discuss/496/always-time-limit-exceeded @@ -44,9 +65,13 @@ def minCut(self, s): # s[:index + 1] requires at most `index` cuts min_cut = index for cursor in range(index + 1): + # if the char at index can form palindrom with the sequence immediately prior to it, + # the min_cut at index is (the min_cut prior to the palindrom) + 1 if s[cursor] == s[index] and (index - cursor <= 2 or (cursor + 1, index - 1) in palindromes): palindromes.add((cursor, index)) + # note: can add a sentiel to the beginning of `cuts` as `-1`, + # or set cuts[0] = 0 and offset index by 1 if cursor == 0: # do not shortcut and continue here; # substring will not be checked for diff --git a/path_sum_ii.py b/path_sum_ii.py index e85580a..005ea7d 100755 --- a/path_sum_ii.py +++ b/path_sum_ii.py @@ -7,7 +7,7 @@ """ # https://oj.leetcode.com/problems/path-sum-ii/ -# tags: easy, tree, dfs, sum +# tags: medium, tree, dfs, sum """ Given a binary tree and a sum, find all root-to-leaf paths where each path's sum equals the given sum. @@ -60,4 +60,3 @@ def check_sum(node, sub_sum): check_sum(root, sum) return res - diff --git a/permutation_sequence.py b/permutation_sequence.py index 6361805..2ea9544 100755 --- a/permutation_sequence.py +++ b/permutation_sequence.py @@ -7,6 +7,7 @@ """ # https://oj.leetcode.com/problems/permutation-sequence/ +# tags: medium / hard, permutation, numbers, logic """ The set [1,2,3,…,n] contains a total of n! unique permutations. diff --git a/permutations.py b/permutations.py index 4953242..04e0e39 100755 --- a/permutations.py +++ b/permutations.py @@ -7,7 +7,7 @@ """ # https://oj.leetcode.com/problems/permutations/ -# tags: medium, numbers, permutation, dp, recursion +# tags: medium, numbers, permutation, dp, recursion, generator """ Given a collection of numbers, return all possible permutations. @@ -17,18 +17,39 @@ [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], and [3,2,1]. """ +# 20240413 -- generator +class Solution: + def permute(self, nums: List[int]) -> List[List[int]]: + + def perm(array): + if len(array) <= 1: + yield array + # alternatively, `return` here directly + + else: + for i, cur in enumerate(array): + rest = array[:i] + array[i+1:] + for each in perm(rest): + yield each + [cur] + + return list(perm(nums)) + + + class Solution: # @param num, a list of integer # @return a list of lists of integers def permute(self, nums): - if len(array) <= 1: - return [array] - - all_perm = [] - for i in xrange(len(array)): - single = array[i] - rest = array[:i] + array[i+1:] - for each in perm(rest): - all_perm.append(each + [single]) + + def perm(array): + if len(array) <= 1: + return [array] + all_perm = [] + for i in xrange(len(array)): + single = array[i] + rest = array[:i] + array[i+1:] + for each in perm(rest): + all_perm.append(each + [single]) + return all_perm - return all_perm + return perm(nums) diff --git a/plus_one.py b/plus_one.py index 591e0c0..14060a4 100755 --- a/plus_one.py +++ b/plus_one.py @@ -7,6 +7,7 @@ """ # https://oj.leetcode.com/problems/plus-one/ +# tags: easy, numbers, array """ Given a non-negative number represented as an array of digits, plus one to the number. diff --git a/recover_binary_search_tree.py b/recover_binary_search_tree.py index 094efef..40f04bc 100755 --- a/recover_binary_search_tree.py +++ b/recover_binary_search_tree.py @@ -7,7 +7,7 @@ """ # https://oj.leetcode.com/problems/recover-binary-search-tree/ -# tags: medium / hard, tree, traversal, optimization, recursion +# tags: medium / hard, tree, traversal, optimization, recursion, tricky """ Two elements of a binary search tree (BST) are swapped by mistake. diff --git a/restore_ip_address.py b/restore_ip_address.py index 414d7a4..5a76049 100755 --- a/restore_ip_address.py +++ b/restore_ip_address.py @@ -6,7 +6,8 @@ Created by Shengwei on 2014-07-04. """ -# https://oj.leetcode.com/problems/restore-ip-addresses/ +# https://oj.leetcode.com/discuss/77/restore-ip-addresses +# tags: medium, string, dp, generator, dfs """ Given a string containing only digits, restore it by returning all possible valid IP address combinations. @@ -17,9 +18,6 @@ return ["255.255.11.135", "255.255.111.35"]. (Order does not matter) """ -# https://oj.leetcode.com/discuss/77/restore-ip-addresses -# tags: medium, string, dp, generator, dfs - # TODO: DP or 3 loops # put 3 dots in len(s) - 1 places, but the distance between two dots must be 1 - 3, # so it's must be less than 3 * 3 * 3 choices in total diff --git a/scramble_string.py b/scramble_string.py index 65fb875..edd20ef 100755 --- a/scramble_string.py +++ b/scramble_string.py @@ -48,7 +48,9 @@ Given two strings s1 and s2 of the same length, determine if s2 is a scrambled string of s1. """ -# https://oj.leetcode.com/discuss/3632/any-better-solution +# https://leetcode.com/problems/scramble-string/solutions/29445/any-better-solution/ +# (https://oj.leetcode.com/discuss/3632/any-better-solution) +# https://leetcode.com/problems/scramble-string/solutions/29396/simple-iterative-dp-java-solution-with-explanation/ """ TODO: memorized version: diff --git a/spiral_matrix.py b/spiral_matrix.py index ad2f52a..d161b29 100755 --- a/spiral_matrix.py +++ b/spiral_matrix.py @@ -7,7 +7,7 @@ """ # https://oj.leetcode.com/problems/spiral-matrix/ -# tags: medium / hard, matrix, spacial, edge cases +# tags: medium / hard, matrix, spacial, edge cases, generator """ Given a matrix of m x n elements (m rows, n columns), return all elements of the matrix in spiral order. @@ -23,6 +23,9 @@ You should return [1,2,3,6,9,8,7,4,5]. """ +# https://leetcode.com/problems/spiral-matrix/solutions/20571/1-liner-in-python-ruby/ + + # https://oj.leetcode.com/discuss/3925/1-2-4-3-is-not-a-wrong-answer # alternatives that don't work (m rows and n cols): # 1. [0][0,n-2,1] + [0,m-2,1][n-1] + [m-1][n-1,1,-1] + [m-1,1,-1][0] diff --git a/strStr.py b/strStr.py index 0201d91..cd92939 100755 --- a/strStr.py +++ b/strStr.py @@ -6,6 +6,7 @@ Created by Shengwei on 2014-07-28. """ +# https://leetcode.com/problems/find-the-index-of-the-first-occurrence-in-a-string/description/ # https://oj.leetcode.com/problems/implement-strstr/ # tags: medium / hard, string, matching @@ -19,6 +20,47 @@ # http://leetcode.com/2010/10/implement-strstr-to-find-substring-in.html # https://gist.github.com/senvey/b602a0fc7ef39daf4b41 + +# 20180502 +class Solution: + # @param haystack, a string + # @param needle, a string + # @return a string or None + def strStr(self, haystack, needle): + + # note: when the first char doesn't match, needle + # needs to move forward, so table[0] must be -1; + # when the second char doesn't match, it must + # start with the first char, so table[1] must be 0 + table = [-1] + i, j = 0, -1 + while i < len(needle): + if j == -1 or needle[i] == needle[j]: + # no match whatsoever, or found a match + # for needle[i] at index j, update table: + # table[i + 1] == j + 1; + # move forward to process next i + i += 1 + j += 1 + table.append(j) + else: + j = table[j] + + i = j = 0 + while i < len(haystack) and j < len(needle): + if j == -1 or haystack[i] == needle[j]: + i += 1 + j += 1 + else: + j = table[j] + + if j == len(needle): + return i - j + + return -1 + + +# 20140728 class Solution: # @param haystack, a string # @param needle, a string @@ -44,7 +86,7 @@ def strStr(self, haystack, needle): j = table[j] i = j = 0 - for i < len(haystack) and j < len(needle): + while i < len(haystack) and j < len(needle): if j == -1 or haystack[i] == needle[j]: i += 1 j += 1 diff --git a/subsets.py b/subsets.py index ebbf150..f154e8c 100755 --- a/subsets.py +++ b/subsets.py @@ -7,7 +7,7 @@ """ # https://oj.leetcode.com/problems/subsets/ -# tags: easy / medium, numbers, set, combination, dfs +# tags: easy / medium, numbers, set, combination, dfs, generator """ Given a set of distinct integers, S, return all possible subsets. @@ -32,6 +32,25 @@ # TODO: different ways to do it + +# 20240415 - generator +class Solution: + def subsets(self, nums: List[int]) -> List[List[int]]: + + def gen(array): + if not array: + yield [] + return + + for subset in gen(array[1:]): + yield subset + yield [array[0]] + subset + + return list(gen(nums)) + + + + class Solution: # @param S, a list of integer # @return a list of lists of integer diff --git a/unique_path.py b/unique_path.py index a4419a6..9e32abe 100755 --- a/unique_path.py +++ b/unique_path.py @@ -24,7 +24,7 @@ # https://oj.leetcode.com/discuss/383/solve-unique-paths-with-linear-algorithm -# Improvoment: only need O(min(m,n)) space +# Improvement: only need O(min(m,n)) space class Solution: # @return an integer diff --git a/validate_binary_search_tree.py b/validate_binary_search_tree.py index 30ff25f..e0f6f87 100755 --- a/validate_binary_search_tree.py +++ b/validate_binary_search_tree.py @@ -47,5 +47,6 @@ def check(node, min_val, max_val): return (check(left, min_val, node.val) and check(right, node.val, max_val)) - # return check(root, -sys.maxint - 1, sys.maxint) + # alternative: return check(root, -sys.maxint - 1, sys.maxint) + # Py3: sys.maxsize return check(root, -9223372036854775808, 9223372036854775807) From c773b5e82c6ee071dd7746680c512b001649be83 Mon Sep 17 00:00:00 2001 From: Shengwei Li Date: Mon, 6 May 2024 11:23:28 -0400 Subject: [PATCH 06/16] done going through all solved solutions in the repo --- 124_binary_tree_maximum_path_sum.py | 1 + 128_longest_consecutive_sequence.py | 3 +- ...m_Number_of_Events_That_Can_Be_Attended.py | 5 +- 234_Palindrome_Linked_List.py | 3 + 297_Serialize_and_Deserialize_Binary_Tree.py | 2 +- 301_Remove_Invalid_Parentheses.py | 110 ++++++++++++++++++ 410_Split_Array_Largest_Sum.py | 3 +- 437_Path_Sum_III.py | 3 +- 543_Diameter_of_Binary_Tree.py | 27 ++++- 680_Valid_Palindrome_II.py | 8 +- 921_Minimum_Add_to_Make_Parentheses_Valid.py | 4 +- 11 files changed, 156 insertions(+), 13 deletions(-) diff --git a/124_binary_tree_maximum_path_sum.py b/124_binary_tree_maximum_path_sum.py index f1a90d2..23ae9aa 100755 --- a/124_binary_tree_maximum_path_sum.py +++ b/124_binary_tree_maximum_path_sum.py @@ -6,6 +6,7 @@ Created by Shengwei on 2014-07-20. """ +# https://leetcode.com/problems/binary-tree-maximum-path-sum/description/ # https://oj.leetcode.com/problems/binary-tree-maximum-path-sum/ # tags: medium / hard, tree, recursion, sum, dfs diff --git a/128_longest_consecutive_sequence.py b/128_longest_consecutive_sequence.py index f7c7f1b..e2a737e 100755 --- a/128_longest_consecutive_sequence.py +++ b/128_longest_consecutive_sequence.py @@ -6,8 +6,9 @@ Created by Shengwei on 2014-07-13. """ +# https://leetcode.com/problems/longest-consecutive-sequence/description/ # https://oj.leetcode.com/problems/longest-consecutive-sequence/ -# tags: hard, array, numbers, hashtable, longest, tricky +# tags: hard, array, numbers, hashtable, longest, tricky, union-find, disjoint set """ Given an unsorted array of integers, find the length of the longest consecutive elements sequence. diff --git a/1353_Maximum_Number_of_Events_That_Can_Be_Attended.py b/1353_Maximum_Number_of_Events_That_Can_Be_Attended.py index 002ffc9..5f3f01a 100755 --- a/1353_Maximum_Number_of_Events_That_Can_Be_Attended.py +++ b/1353_Maximum_Number_of_Events_That_Can_Be_Attended.py @@ -4,6 +4,9 @@ 1353. Maximum Number of Events That Can Be Attended Created by Shengwei on 2022-04-18. + +Used: +* FB: https://www.1point3acres.com/bbs/thread-885226-1-1.html """ # https://leetcode.com/problems/maximum-number-of-events-that-can-be-attended/ @@ -40,8 +43,6 @@ 1 <= startDayi <= endDayi <= 105 """ -# https://www.1point3acres.com/bbs/thread-885226-1-1.html (FB) - class Solution: def maxEvents(self, events: List[List[int]]) -> int: """Loop through all the events: diff --git a/234_Palindrome_Linked_List.py b/234_Palindrome_Linked_List.py index 3276dcf..d3ee92b 100644 --- a/234_Palindrome_Linked_List.py +++ b/234_Palindrome_Linked_List.py @@ -44,6 +44,9 @@ # self.next = next class Solution: def isPalindrome(self, head: Optional[ListNode]) -> bool: + """Use slow/fast cursors to reverse the first half of the list, + and then compare pairs of nodes from the middle + """ rev = None slow = fast = head while fast and fast.next: diff --git a/297_Serialize_and_Deserialize_Binary_Tree.py b/297_Serialize_and_Deserialize_Binary_Tree.py index e84b602..bce1137 100755 --- a/297_Serialize_and_Deserialize_Binary_Tree.py +++ b/297_Serialize_and_Deserialize_Binary_Tree.py @@ -34,4 +34,4 @@ -1000 <= Node.val <= 1000 """ -# https://leetcode.com/problems/serialize-and-deserialize-binary-tree/discuss/74259/Recursive-preorder-Python-and-C%2B%2B-O(n) +# https://leetcode.com/problems/serialize-and-deserialize-binary-tree/solutions/74259/recursive-preorder-python-and-c-o-n/ diff --git a/301_Remove_Invalid_Parentheses.py b/301_Remove_Invalid_Parentheses.py index 5457241..bb83d8d 100755 --- a/301_Remove_Invalid_Parentheses.py +++ b/301_Remove_Invalid_Parentheses.py @@ -4,6 +4,9 @@ 301. Remove Invalid Parentheses Created by Shengwei on 2022-04-21. + +Used: +* Google onsite interview (myself) """ # https://leetcode.com/problems/remove-invalid-parentheses/ @@ -38,8 +41,21 @@ There will be at most 20 parentheses in s. """ +"""another thought (20240505): + + - remove excessive ')' from left to right, and record positions where to remove them. + this will be a list of possible solutions, where each solution is a list of indices for ')' + - reverse the string including swap '(' and ')', and repeat the step above + - multiple the solutions from both steps, one for excessive '(' and another for excessive ')' + +this should avoid duplicate processing of reversed strings in the original solutions below. +""" + + + # https://leetcode.com/problems/remove-invalid-parentheses/discuss/75027/Easy-Short-Concise-and-Fast-Java-DFS-3-ms-solution +# 20190407 class Solution(object): def removeInvalidParentheses(self, s): """ @@ -51,6 +67,7 @@ def removeInvalidParentheses(self, s): def remove(s_to_check, start_from, pointer, left_to_right): ''' start_from: start point to check if the char is ')'; it should not be ')' to begin with + (this prevents repeatitive checks) pointer: loop through the string `s` ''' counter = 0 @@ -81,7 +98,100 @@ def remove(s_to_check, start_from, pointer, left_to_right): if counter > 0: remove(reversed_s, 0, 0, False) else: + # counter == 0, all excessive parentheses have been removed ret.append(s_to_check) if left_to_right else ret.append(reversed_s) remove(s, 0, 0, True) return ret + + +class Solution(object): + def removeInvalidParentheses(self, s): + """ + :type s: str + :rtype: List[str] + """ + + def check(s_to_check, start_from, pointer, left_to_right): + counter = 0 + while pointer < len(s_to_check): + if s_to_check[pointer] == '(': + counter += 1 + elif s_to_check[pointer] == ')': + counter -= 1 + + if counter < 0: + break + pointer += 1 + + if counter >= 0: + mapping = {'(': ')', ')': '('} + reversed_s = ''.join(mapping.get(ch, ch) for ch in reversed(s_to_check)) + if counter > 0: + for to_check in check(reversed_s, 0, 0, not left_to_right): + yield to_check + else: + # Note: add different string depending on direction of checking + if left_to_right: yield s_to_check + else: yield reversed_s + + else: + # pointer points to the char of excessive ')' + i = start_from + while i <= pointer: + # should check `i != start_from` instead of `i > 0`: + # the sequence prior to `start_from` has aligned + if s_to_check[i] == ')' and (i == start_from or s_to_check[i - 1] != ')'): + for to_check in check(s_to_check[:i] + s_to_check[i+1:], i, pointer, left_to_right): + yield to_check + + i += 1 + + return list(check(s, 0, 0, True)) + + +# iterative +class Solution(object): + def removeInvalidParentheses(self, s): + """ + :type s: str + :rtype: List[str] + """ + + ret = [] + to_check = [(s, 0, 0, True)] + + while to_check: + s_to_check, start_from, pointer, left_to_right = to_check.pop() + counter = 0 + while pointer < len(s_to_check): + if s_to_check[pointer] == '(': + counter += 1 + elif s_to_check[pointer] == ')': + counter -= 1 + + if counter < 0: + break + pointer += 1 + + if counter >= 0: + mapping = {'(': ')', ')': '('} + reversed_s = ''.join(mapping.get(ch, ch) for ch in reversed(s_to_check)) + if counter > 0: + to_check.append((reversed_s, 0, 0, not left_to_right)) + else: + # Note: add different string depending on direction of checking + ret.append(s_to_check) if left_to_right else ret.append(reversed_s) + + else: + # pointer points to the char of excessive ')' + i = start_from + while i <= pointer: + # should check `i != start_from` instead of `i > 0`: + # the sequence prior to `start_from` has aligned + if s_to_check[i] == ')' and (i == start_from or s_to_check[i - 1] != ')'): + to_check.append((s_to_check[:i] + s_to_check[i+1:], i, pointer, left_to_right)) + + i += 1 + + return ret \ No newline at end of file diff --git a/410_Split_Array_Largest_Sum.py b/410_Split_Array_Largest_Sum.py index 709139a..2e0b2ba 100644 --- a/410_Split_Array_Largest_Sum.py +++ b/410_Split_Array_Largest_Sum.py @@ -7,7 +7,7 @@ """ # https://leetcode.com/problems/split-array-largest-sum/description/ -# tags: hard, array, number, binary +# tags: hard, array, number, binary, dp """ Given an integer array nums and an integer k, split nums into k non-empty subarrays such that the largest sum of any subarray is minimized. @@ -40,6 +40,7 @@ """ # greedy: go from left to right and sum up to a limit --> results in the least number of splits +# alternative (dp): https://leetcode.com/problems/split-array-largest-sum/solutions/89846/python-solution-with-detailed-explanation/ class Solution: def splitArray(self, nums: List[int], k: int) -> int: diff --git a/437_Path_Sum_III.py b/437_Path_Sum_III.py index 3219782..aa85405 100644 --- a/437_Path_Sum_III.py +++ b/437_Path_Sum_III.py @@ -49,6 +49,7 @@ # self.right = right class Solution: def pathSum(self, root: Optional[TreeNode], targetSum: int) -> int: + from functools import cache @cache def sub(node, target): @@ -78,4 +79,4 @@ def dfs(node, target): count += 1 return count - return sub(root, targetSum) \ No newline at end of file + return sub(root, targetSum) diff --git a/543_Diameter_of_Binary_Tree.py b/543_Diameter_of_Binary_Tree.py index b54e16e..d60dff8 100644 --- a/543_Diameter_of_Binary_Tree.py +++ b/543_Diameter_of_Binary_Tree.py @@ -3,7 +3,7 @@ """ 543. Diameter of Binary Tree -Created by Shengwei on 2023-10-06. +Created by Shengwei on 2023-11-06. Used: * Meta: https://www.1point3acres.com/bbs/thread-1026007-1-1.html @@ -23,10 +23,10 @@ Example 1: - Input: root = [1,2,3,4,5] Output: 3 Explanation: 3 is the length of the path [4,2,1,3] or [5,2,1,3]. + Example 2: Input: root = [1,2] @@ -47,6 +47,29 @@ # self.right = right +# 20240506 +class Solution: + + def __init__(self): + self.diameter = 0 + + def diameterOfBinaryTree(self, root: Optional[TreeNode]) -> int: + self.check(root) + return self.diameter + + def check(self, node): + if not node: + return 0 + + left_length = self.check(node.left) + right_length = self.check(node.right) + current_diameter = left_length + right_length + self.diameter = max(self.diameter, current_diameter) + + return max(left_length, right_length) + 1 + + +# 20231107 class Solution: def diameterOfBinaryTree(self, root: Optional[TreeNode]) -> int: diff --git a/680_Valid_Palindrome_II.py b/680_Valid_Palindrome_II.py index db9d941..3da6751 100755 --- a/680_Valid_Palindrome_II.py +++ b/680_Valid_Palindrome_II.py @@ -7,7 +7,7 @@ """ # https://leetcode.com/problems/valid-palindrome-ii/ -# tags: easy / medium, string +# tags: easy / medium, string, palindrome """ Given a string s, return true if the s can be palindrome after deleting at most one character from it. @@ -70,9 +70,6 @@ class Solution: def validPalindrome(self, s: str) -> bool: if not s: return True - def is_pal(s): - return s == s[::-1] - left, right = 0, len(s) - 1 while left < right: if s[left] != s[right]: @@ -80,4 +77,7 @@ def is_pal(s): left += 1 right -= 1 + def is_pal(s): + return s == s[::-1] + return is_pal(s[left+1:right+1]) or is_pal(s[left:right]) diff --git a/921_Minimum_Add_to_Make_Parentheses_Valid.py b/921_Minimum_Add_to_Make_Parentheses_Valid.py index 8241993..ce4bb82 100644 --- a/921_Minimum_Add_to_Make_Parentheses_Valid.py +++ b/921_Minimum_Add_to_Make_Parentheses_Valid.py @@ -29,11 +29,13 @@ Input: s = "())" Output: 1 + + Example 2: Input: s = "(((" Output: 3 - + Constraints: From 056e7783dd61a308779f736ffacb39cfe2220974 Mon Sep 17 00:00:00 2001 From: Shengwei Li Date: Mon, 13 May 2024 10:25:17 -0400 Subject: [PATCH 07/16] prepared interview for Datadog and Tiktok --- ...th_in_a_Grid_with_Obstacles_Elimination.py | 47 +++++++ 163_Missing_Ranges.py | 3 + 1762_Buildings_With_an_Ocean_View_Level.py | 3 + ...er_of_Swaps_to_Make_the_String_Balanced.py | 67 +++++++++ 200_Number_of_Islands.py | 48 +++++++ 239_Sliding_Window_Maximum.py | 78 +++++++++++ 274_H-Index.py | 54 ++++++++ ...hs_That_Can_Form_a_Palindrome_in_a_Tree.py | 54 ++++++++ 499_The_Maze_III.py | 128 ++++++++++++++++++ 670_Maximum_Swap.py | 97 +++++++++++++ 856_Score_of_Parentheses.py | 118 ++++++++++++++++ 969_Pancake_Sorting.py | 54 ++++++++ 12 files changed, 751 insertions(+) create mode 100644 1293_Shortest_Path_in_a_Grid_with_Obstacles_Elimination.py create mode 100644 1963_Minimum_Number_of_Swaps_to_Make_the_String_Balanced.py create mode 100644 200_Number_of_Islands.py create mode 100644 239_Sliding_Window_Maximum.py create mode 100644 274_H-Index.py create mode 100644 2791_Count_Paths_That_Can_Form_a_Palindrome_in_a_Tree.py create mode 100644 499_The_Maze_III.py create mode 100644 670_Maximum_Swap.py create mode 100644 856_Score_of_Parentheses.py create mode 100644 969_Pancake_Sorting.py diff --git a/1293_Shortest_Path_in_a_Grid_with_Obstacles_Elimination.py b/1293_Shortest_Path_in_a_Grid_with_Obstacles_Elimination.py new file mode 100644 index 0000000..2cc7e0f --- /dev/null +++ b/1293_Shortest_Path_in_a_Grid_with_Obstacles_Elimination.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +1293. Shortest Path in a Grid with Obstacles Elimination + +Created by Shengwei on 2025-05-09. + +Used: +- TikTok: https://www.1point3acres.com/bbs/thread-1063616-1-1.html +""" + +# https://leetcode.com/problems/shortest-path-in-a-grid-with-obstacles-elimination/description/ +# tags: hard, matrix, bfs, shortest + +""" +You are given an m x n integer matrix grid where each cell is either 0 (empty) or 1 (obstacle). You can move up, down, left, or right from and to an empty cell in one step. + +Return the minimum number of steps to walk from the upper left corner (0, 0) to the lower right corner (m - 1, n - 1) given that you can eliminate at most k obstacles. If it is not possible to find such walk return -1. + + +Example 1: + +Input: grid = [[0,0,0],[1,1,0],[0,0,0],[0,1,1],[0,0,0]], k = 1 +Output: 6 +Explanation: +The shortest path without eliminating any obstacle is 10. +The shortest path with one obstacle elimination at position (3,2) is 6. Such path is (0,0) -> (0,1) -> (0,2) -> (1,2) -> (2,2) -> (3,2) -> (4,2). + + +Example 2: + +Input: grid = [[0,1,1],[1,1,1],[1,0,0]], k = 1 +Output: -1 +Explanation: We need to eliminate at least two obstacles to find such a walk. + + +Constraints: + +m == grid.length +n == grid[i].length +1 <= m, n <= 40 +1 <= k <= m * n +grid[i][j] is either 0 or 1. +grid[0][0] == grid[m - 1][n - 1] == 0 +""" + +# https://leetcode.com/problems/shortest-path-in-a-grid-with-obstacles-elimination/solutions/451787/python-o-m-n-k-bfs-solution-with-explanation/ diff --git a/163_Missing_Ranges.py b/163_Missing_Ranges.py index 06f5821..48a1c64 100644 --- a/163_Missing_Ranges.py +++ b/163_Missing_Ranges.py @@ -4,6 +4,9 @@ 163. Missing Ranges Created by Shengwei on 2023-10-28. + +Used: +- TikTok (20240510): https://www.1point3acres.com/bbs/thread-1063465-1-1.html """ # Locked diff --git a/1762_Buildings_With_an_Ocean_View_Level.py b/1762_Buildings_With_an_Ocean_View_Level.py index 34244a6..e00da9a 100644 --- a/1762_Buildings_With_an_Ocean_View_Level.py +++ b/1762_Buildings_With_an_Ocean_View_Level.py @@ -4,6 +4,9 @@ 1762. Buildings With an Ocean View Level Created by Shengwei on 2023-10-29. + +Used: +- TikTok (20240510): https://www.1point3acres.com/bbs/thread-1061817-1-1.html """ # Locked: https://leetcode.ca/2021-04-14-1762-Buildings-With-an-Ocean-View/ diff --git a/1963_Minimum_Number_of_Swaps_to_Make_the_String_Balanced.py b/1963_Minimum_Number_of_Swaps_to_Make_the_String_Balanced.py new file mode 100644 index 0000000..6128f93 --- /dev/null +++ b/1963_Minimum_Number_of_Swaps_to_Make_the_String_Balanced.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +1963. Minimum Number of Swaps to Make the String Balanced + +Created by Shengwei on 2025-05-12. + +Used: +- TikTok - https://www.1point3acres.com/bbs/thread-1061850-1-1.html +""" + +# https://leetcode.com/problems/minimum-number-of-swaps-to-make-the-string-balanced/description/ +# tags: medium, parentheses, logic + +""" +You are given a 0-indexed string s of even length n. The string consists of exactly n / 2 opening brackets '[' and n / 2 closing brackets ']'. + +A string is called balanced if and only if: + +It is the empty string, or +It can be written as AB, where both A and B are balanced strings, or +It can be written as [C], where C is a balanced string. +You may swap the brackets at any two indices any number of times. + +Return the minimum number of swaps to make s balanced. + + + +Example 1: + +Input: s = "][][" +Output: 1 +Explanation: You can make the string balanced by swapping index 0 with index 3. +The resulting string is "[[]]". + +Example 2: + +Input: s = "]]][[[" +Output: 2 +Explanation: You can do the following to make the string balanced: +- Swap index 0 with index 4. s = "[]][][". +- Swap index 1 with index 5. s = "[[][]]". +The resulting string is "[[][]]". + +Example 3: + +Input: s = "[]" +Output: 0 +Explanation: The string is already balanced. + + +Constraints: + +n == s.length +2 <= n <= 106 +n is even. +s[i] is either '[' or ']'. +The number of opening brackets '[' equals n / 2, and the number of closing brackets ']' equals n / 2. +""" + +""" +https://leetcode.com/problems/minimum-number-of-swaps-to-make-the-string-balanced/solutions/1390319/how-to-reach-the-optimal-solution-explained-with-intuition-and-diagram/ +One sentence explanation: +First cancel out all the valid pairs, then you will be left with something like]]][[[, and the answer will be ceil(m/2). Where m is the number of pairs left. + +https://leetcode.com/problems/minimum-number-of-swaps-to-make-the-string-balanced/solutions/1390193/python-greedy-o-n-explained/ +""" \ No newline at end of file diff --git a/200_Number_of_Islands.py b/200_Number_of_Islands.py new file mode 100644 index 0000000..1816662 --- /dev/null +++ b/200_Number_of_Islands.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +200. Number of Islands + +Created by Shengwei on 2025-05-08. + +Used: +- TikTok: https://www.1point3acres.com/bbs/thread-1064304-1-1.html +""" + +# https://leetcode.com/problems/number-of-islands/description/ +# tags: medium + +""" +Given an m x n 2D binary grid grid which represents a map of '1's (land) and '0's (water), return the number of islands. + +An island is surrounded by water and is formed by connecting adjacent lands horizontally or vertically. You may assume all four edges of the grid are all surrounded by water. + + +Example 1: + +Input: grid = [ + ["1","1","1","1","0"], + ["1","1","0","1","0"], + ["1","1","0","0","0"], + ["0","0","0","0","0"] +] +Output: 1 + +Example 2: + +Input: grid = [ + ["1","1","0","0","0"], + ["1","1","0","0","0"], + ["0","0","1","0","0"], + ["0","0","0","1","1"] +] +Output: 3 + + +Constraints: + +m == grid.length +n == grid[i].length +1 <= m, n <= 300 +grid[i][j] is '0' or '1'. +""" diff --git a/239_Sliding_Window_Maximum.py b/239_Sliding_Window_Maximum.py new file mode 100644 index 0000000..a78ac1c --- /dev/null +++ b/239_Sliding_Window_Maximum.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +239. Sliding Window Maximum + +Created by Shengwei on 2025-05-08, submitted on 2018-09-22. + +Used: +- TikTok: https://www.1point3acres.com/bbs/thread-1064522-1-1.html +""" + +# https://leetcode.com/problems/sliding-window-maximum/description/ +# tags: hard, array, window, tricky, starred + +""" +You are given an array of integers nums, there is a sliding window of size k which is moving from the very left of the array to the very right. You can only see the k numbers in the window. Each time the sliding window moves right by one position. + +Return the max sliding window. + + + +Example 1: + +Input: nums = [1,3,-1,-3,5,3,6,7], k = 3 +Output: [3,3,5,5,6,7] +Explanation: +Window position Max +--------------- ----- +[1 3 -1] -3 5 3 6 7 3 + 1 [3 -1 -3] 5 3 6 7 3 + 1 3 [-1 -3 5] 3 6 7 5 + 1 3 -1 [-3 5 3] 6 7 5 + 1 3 -1 -3 [5 3 6] 7 6 + 1 3 -1 -3 5 [3 6 7] 7 +Example 2: + +Input: nums = [1], k = 1 +Output: [1] + + +Constraints: + +1 <= nums.length <= 105 +-104 <= nums[i] <= 104 +1 <= k <= nums.length +""" + + +class Solution(object): + def maxSlidingWindow(self, nums, k): + """ + :type nums: List[int] + :type k: int + :rtype: List[int] + """ + if not nums: return [] + + from collections import deque + + res = [] + holder = deque() # hold indices of possible max values + for i in range(len(nums)): + # remove from tail whichever numbers at the index are + # smaller than the one at current index i + while holder and nums[holder[-1]] < nums[i]: + holder.pop() + holder.append(i) + + # remove head of the stack if the index is out of window + start = i - k + 1 + if holder[0] < start: + holder.popleft() + + # start to add numbers to result until the first window fills + if start >= 0: + res.append(nums[holder[0]]) + + return res diff --git a/274_H-Index.py b/274_H-Index.py new file mode 100644 index 0000000..2dc49ed --- /dev/null +++ b/274_H-Index.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +274. H-Index + +Created by Shengwei on 2025-05-10. + +Used: +- TikTok: https://www.1point3acres.com/bbs/thread-1065902-1-1.html +""" + +# https://leetcode.com/problems/h-index/description/ +# tags: medium / hard, logic, array, tricky + +""" +Given an array of integers citations where citations[i] is the number of citations a researcher received for their ith paper, return the researcher's h-index. + +According to the definition of h-index on Wikipedia: The h-index is defined as the maximum value of h such that the given researcher has published at least h papers that have each been cited at least h times. + + + +Example 1: + +Input: citations = [3,0,6,1,5] +Output: 3 +Explanation: [3,0,6,1,5] means the researcher has 5 papers in total and each of them had received 3, 0, 6, 1, 5 citations respectively. +Since the researcher has 3 papers with at least 3 citations each and the remaining two with no more than 3 citations each, their h-index is 3. +Example 2: + +Input: citations = [1,3,1] +Output: 1 + + +Constraints: + +n == citations.length +1 <= n <= 5000 +0 <= citations[i] <= 1000 +""" + +# https://leetcode.com/problems/h-index/solutions/71055/1-line-python-solution/ +class Solution: + def hIndex(self, citations: List[int]) -> int: + return sum(i < j for i, j in enumerate(sorted(citations, reverse=True))) + + +# from faster solutions +class Solution: + def hIndex(self, citations: List[int]) -> int: + h = len(citations) + for i in sorted(citations): + if i >= h: + return h + h -= 1 diff --git a/2791_Count_Paths_That_Can_Form_a_Palindrome_in_a_Tree.py b/2791_Count_Paths_That_Can_Form_a_Palindrome_in_a_Tree.py new file mode 100644 index 0000000..08897b7 --- /dev/null +++ b/2791_Count_Paths_That_Can_Form_a_Palindrome_in_a_Tree.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +2791. Count Paths That Can Form a Palindrome in a Tree + +Created by Shengwei on 2025-05-08. + +Used: +- TikTok: https://www.1point3acres.com/bbs/thread-1063645-1-1.html +""" + +# https://leetcode.com/problems/count-paths-that-can-form-a-palindrome-in-a-tree/description/ +# tags: hard, palindrome, tree + +""" +You are given a tree (i.e. a connected, undirected graph that has no cycles) rooted at node 0 consisting of n nodes numbered from 0 to n - 1. The tree is represented by a 0-indexed array parent of size n, where parent[i] is the parent of node i. Since node 0 is the root, parent[0] == -1. + +You are also given a string s of length n, where s[i] is the character assigned to the edge between i and parent[i]. s[0] can be ignored. + +Return the number of pairs of nodes (u, v) such that u < v and the characters assigned to edges on the path from u to v can be rearranged to form a palindrome. + +A string is a palindrome when it reads the same backwards as forwards. + + +Example 1: + +Input: parent = [-1,0,0,1,1,2], s = "acaabc" +Output: 8 +Explanation: The valid pairs are: +- All the pairs (0,1), (0,2), (1,3), (1,4) and (2,5) result in one character which is always a palindrome. +- The pair (2,3) result in the string "aca" which is a palindrome. +- The pair (1,5) result in the string "cac" which is a palindrome. +- The pair (3,5) result in the string "acac" which can be rearranged into the palindrome "acca". + + +Example 2: + +Input: parent = [-1,0,0,0,0], s = "aaaaa" +Output: 10 +Explanation: Any pair of nodes (u,v) where u < v is valid. + + +Constraints: + +n == parent.length == s.length +1 <= n <= 105 +0 <= parent[i] <= n - 1 for all i >= 1 +parent[0] == -1 +parent represents a valid tree. +s consists of only lowercase English letters. +""" + +# https://leetcode.com/problems/count-paths-that-can-form-a-palindrome-in-a-tree/solutions/3803579/java-solution-using-bitmask-with-detailed-explanation/ +# https://leetcode.com/problems/count-paths-that-can-form-a-palindrome-in-a-tree/solutions/3803762/java-c-python-bit-mask/ diff --git a/499_The_Maze_III.py b/499_The_Maze_III.py new file mode 100644 index 0000000..e66fe6f --- /dev/null +++ b/499_The_Maze_III.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +499. The Maze III + +Created by Shengwei on 2025-05-09. + +Used: +- TikTok: https://www.1point3acres.com/bbs/thread-1063465-1-1.html +""" + +# locked +# tags: hard, matrix, shortest + +""" +There is a ball in a maze with empty spaces and walls. The ball can go through empty spaces by rolling up(u),down(d),left(l) or right(r), but it won't stop rolling until hitting a wall. When the ball stops, it could choose the next direction. There is also a hole in this maze. The ball will drop into the hole if it rolls on to the hole. + +Given the ball position, the hole position and the maze, find out how the ball could drop into the hole by moving the shortest distance. The distance is defined by the number of empty spaces traveled by the ball from the start position (excluded) to the hole (included). Output the moving directions by using 'u', 'd', 'l' and 'r'. Since there could be several different shortest ways, you should output the lexicographically smallest way. If the ball cannot reach the hole, output "impossible". + +The maze is represented by a binary 2D array. 1 means the wall and 0 means the empty space. You may assume that the borders of the maze are all walls. The ball and the hole coordinates are represented by row and column indexes. + +Example 1: + +Copy +Input 1: a maze represented by a 2D array + +0 0 0 0 0 +1 1 0 0 1 +0 0 0 0 0 +0 1 0 0 1 +0 1 0 0 0 + +Input 2: ball coordinate (rowBall, colBall) = (4, 3) +Input 3: hole coordinate (rowHole, colHole) = (0, 1) + +Output: "lul" + +Explanation: There are two shortest ways for the ball to drop into the hole. +The first way is left -> up -> left, represented by "lul". +The second way is up -> left, represented by 'ul'. +Both ways have shortest distance 6, but the first way is lexicographically smaller, +because 'l' < 'u'. So the output is "lul". +Example 2: + +Copy +Input 1: a maze represented by a 2D array + +0 0 0 0 0 +1 1 0 0 1 +0 0 0 0 0 +0 1 0 0 1 +0 1 0 0 0 + +Input 2: ball coordinate (rowBall, colBall) = (4, 3) +Input 3: hole coordinate (rowHole, colHole) = (3, 0) + +Output: "impossible" + +Explanation: The ball cannot reach the hole. +""" + +# - https://aaronice.gitbook.io/lintcode/graph_search/the-maze-iii +# - https://xiaoguan.gitbooks.io/leetcode/content/LeetCode/499-The-Maze-III-hard.html + + +from collections import defaultdict, deque, namedtuple + + +class Solution: + def roll(self, matrix, ball_pos, hole_pos): + """Rolls the all to all possible directions, and records the steps to the stop positions. + + Increases steps to check 1 by 1, with all the paths and positions with such steps. + """ + + def is_valid(pos_x, pos_y): + return (0 <= pos_x < len(matrix) + and 0 <= pos_y < len(matrix[0]) + and matrix[pos_x][pos_y] == 0) + + # possible next directions given the current direction; + # we don't want to roll back and it cannot go further in the same direction + possible_dirs = {'l': 'ud', 'r': 'ud', 'u': 'lr', 'd': 'lr'} + dirs = {'l': (0, -1), 'r': (0, 1), 'u': (-1, 0), 'd': (1, 0)} + queues = defaultdict(deque) + # key: steps to the positions + # value: (path, position to reach, next possible directions) + queues[0].append(('', tuple(ball_pos), 'lrud')) + visited = {tuple(ball_pos): 0} + + steps = 0 + res = defaultdict(list) + + # increase steps until the count is greater than the max possible steps, + # also break if the hole could be reached with steps fewer than the next loop + while steps <= max(queues.keys()) and (not res or min(res.keys()) > steps): + # check all possible paths to reach stored positions with current steps + while queues[steps]: + path, (pos_x, pos_y), next_dirs = queues[steps].popleft() + for d in next_dirs: + move_x, move_y = pos_x, pos_y + acc_steps = steps + new_path = path + d + move = dirs[d] + while is_valid(move_x + move[0], move_y + move[1]): + acc_steps += 1 + move_x, move_y = move_x + move[0], move_y + move[1] + if (move_x, move_y) == tuple(hole_pos): + res[acc_steps].append(new_path) + break + + if (acc_steps <= visited.get((move_x, move_y), acc_steps) + and (move_x, move_y) != (pos_x, pos_y) + and (move_x, move_y) != tuple(hole_pos)): + # if move is possible, and can be reached with fewer or equal steps, + # and it's not the hole, record the possible paths + # note: need to record new path in case it's lexicographically smaller + visited[(move_x, move_y)] = acc_steps + queues[acc_steps].append((new_path, (move_x, move_y), possible_dirs[d])) + # print(queues) + + steps += 1 + + if res: + # print(res) + return sorted(res[min(res.keys())])[0] + + return 'impossible' diff --git a/670_Maximum_Swap.py b/670_Maximum_Swap.py new file mode 100644 index 0000000..fcbeb67 --- /dev/null +++ b/670_Maximum_Swap.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +670. Maximum Swap + +Created by Shengwei on 2024-05-07. + +Used: +- TikTok: https://www.1point3acres.com/bbs/thread-1065235-1-1.html +""" + +# https://leetcode.com/problems/maximum-swap/description/ +# tags: medium / hard, numbers, tricky, logic + +""" +You are given an integer num. You can swap two digits at most once to get the maximum valued number. + +Return the maximum valued number you can get. + + + +Example 1: + +Input: num = 2736 +Output: 7236 +Explanation: Swap the number 2 and the number 7. +Example 2: + +Input: num = 9973 +Output: 9973 +Explanation: No swap. + + +Constraints: + +0 <= num <= 108 +""" + +# others: +# - https://leetcode.com/problems/maximum-swap/solutions/185982/straightforward-o-n-python/ +# - this is essentially the same as the "alternative solution", while there is no need +# to actually do the swap as it is for bubble-sort +# - the trick is when to update with max_id -- only when a smaller one for swap is found + + +# alternative solution utilizing one run of bubble-sort, O(n) +class Solution: + def maximumSwap(self, num: int) -> int: + chars = bytearray(str(num), 'utf-8') + for i in range(-1, -len(chars), -1): + if chars[i] > chars[i - 1]: + chars[i], chars[i - 1] = chars[i - 1], chars[i] + # print(''.join(chr(n) for n in chars)) + + for i in range(len(chars)): + if chr(chars[i]) != str(num)[i]: + break + largest, j = chars[i], i + # print(largest, j) + + chars = bytearray(str(num), 'utf-8') + for i in range(-1, -len(chars), -1): + if largest == chars[i]: + break + + chars[i], chars[j] = chars[j], chars[i] + return int(chars.decode()) + + +# this solution is a bit tricky, encoutering lots of edge cases to evolve +# - the oi must be held (as `hold_i`) for repetition and used to swap later +# - `hold_i` has to be reset to 0 correctly (i.e., after repetition) +# - edge cases: 98368, 1993, 91993 +# this solution also requires sorting so it's O(nlogn) +class Solution: + def maximumSwap(self, num: int) -> int: + chars = bytearray(str(num), 'utf-8') + # note: + # - `reversed` returns an iterator, while `sorted` returns a list + # - `sorted` also takes `reverse` arg + cache = sorted(((n, i) for i, n in enumerate(chars)), reverse=True) + # print(cache) + + for i in range(len(cache)): + n, oi = cache[i] + + if i == 0 or cache[i - 1][0] != n: + hold_i = 0 + + if i + 1 < len(cache) and cache[i + 1][0] == n and hold_i < oi: + hold_i = oi + + swap_i = hold_i or oi + if i < swap_i and chars[i] < chars[swap_i]: + chars[i], chars[swap_i] = chars[swap_i], chars[i] + break + return int(chars.decode()) \ No newline at end of file diff --git a/856_Score_of_Parentheses.py b/856_Score_of_Parentheses.py new file mode 100644 index 0000000..872cfa7 --- /dev/null +++ b/856_Score_of_Parentheses.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +856. Score of Parentheses + +Created by Shengwei on 2025-05-10. + +Used: +- TikTok: https://www.1point3acres.com/bbs/thread-1061850-1-1.html +""" + +# https://leetcode.com/problems/score-of-parentheses/description/ +# tags: medium / hard, logic, parantheses, recursion, stack + +""" +Given a balanced parentheses string s, return the score of the string. + +The score of a balanced parentheses string is based on the following rule: + +"()" has score 1. +AB has score A + B, where A and B are balanced parentheses strings. +(A) has score 2 * A, where A is a balanced parentheses string. + + +Example 1: + +Input: s = "()" +Output: 1 + +Example 2: + +Input: s = "(())" +Output: 2 + +Example 3: + +Input: s = "()()" +Output: 2 + + +Constraints: + +2 <= s.length <= 50 +s consists of only '(' and ')'. +s is a balanced parentheses string. +""" + +# v3 +class Solution: + def scoreOfParentheses(self, s: str) -> int: + + stack = [] + for c in s: + if c == '(': + stack.append(c) + else: + if stack[-1] == '(': + stack.pop() + stack.append(1) + else: + score = 0 + while stack[-1] != '(': + score += 2 * stack.pop() + stack.pop() + stack.append(score) + + return sum(stack) + + +# v2 +class Solution: + def scoreOfParentheses(self, s: str) -> int: + + def process(pos): + score = 0 + lc = 0 + while pos < len(s): + if s[pos] == '(': + lc += 1 + if s[pos+1] == ')': # '()' + score += 1 + else: # '((' + t_score, pos = process(pos + 1) + score = 2 * t_score + else: + lc -= 1 # '))' + if pos + 1 < len(s) and s[pos+1] == '(': # ')(' + t_score, pos = process(pos + 1) + score += t_score + + if not lc: + return score, pos + pos += 1 + + return process(0)[0] + + +# v1 +class Solution: + def scoreOfParentheses(self, s: str) -> int: + score = 0 + lc = 0 + start = 0 + for i in range(len(s)): + if s[i] == '(': + lc += 1 + else: + lc -= 1 + + i += 1 + if lc == 0: + sub_s = s[start:i] + if sub_s == '()': + score += 1 + else: + score += 2 * self.scoreOfParentheses(sub_s[1:-1]) + start = i + return score diff --git a/969_Pancake_Sorting.py b/969_Pancake_Sorting.py new file mode 100644 index 0000000..7789ce3 --- /dev/null +++ b/969_Pancake_Sorting.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +969. Pancake Sorting + +Created by Shengwei on 2025-05-12. + +Used: +- TikTok - myself interview 2025-05-10 (modified: not 0...k-1 but random numbers) +""" + +# https://leetcode.com/problems/pancake-sorting/description/ +# tags: medium, array, sorting, logic + +""" +Given an array of integers arr, sort the array by performing a series of pancake flips. + +In one pancake flip we do the following steps: + +Choose an integer k where 1 <= k <= arr.length. +Reverse the sub-array arr[0...k-1] (0-indexed). +For example, if arr = [3,2,1,4] and we performed a pancake flip choosing k = 3, we reverse the sub-array [3,2,1], so arr = [1,2,3,4] after the pancake flip at k = 3. + +Return an array of the k-values corresponding to a sequence of pancake flips that sort arr. Any valid answer that sorts the array within 10 * arr.length flips will be judged as correct. + + + +Example 1: + +Input: arr = [3,2,4,1] +Output: [4,2,4,3] +Explanation: +We perform 4 pancake flips, with k values 4, 2, 4, and 3. +Starting state: arr = [3, 2, 4, 1] +After 1st flip (k = 4): arr = [1, 4, 2, 3] +After 2nd flip (k = 2): arr = [4, 1, 2, 3] +After 3rd flip (k = 4): arr = [3, 2, 1, 4] +After 4th flip (k = 3): arr = [1, 2, 3, 4], which is sorted. +Example 2: + +Input: arr = [1,2,3] +Output: [] +Explanation: The input is already sorted, so there is no need to flip anything. +Note that other answers, such as [3, 3], would also be accepted. + + +Constraints: + +1 <= arr.length <= 100 +1 <= arr[i] <= arr.length +All integers in arr are unique (i.e. arr is a permutation of the integers from 1 to arr.length). +""" + +# https://leetcode.com/problems/pancake-sorting/solutions/214213/java-c-python-straight-forward/ \ No newline at end of file From 8e703cd1ac8f42ab2806aaf3e6e3850175627ce8 Mon Sep 17 00:00:00 2001 From: Shengwei Li Date: Tue, 4 Jun 2024 00:09:05 -0400 Subject: [PATCH 08/16] number of islands --- 1891_Cutting_Ribbons.py | 2 +- 200_Number_of_Islands.py | 78 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/1891_Cutting_Ribbons.py b/1891_Cutting_Ribbons.py index c1a854c..520bdc4 100644 --- a/1891_Cutting_Ribbons.py +++ b/1891_Cutting_Ribbons.py @@ -10,7 +10,7 @@ """ # locked - https://leetcode.ca/2021-07-24-1891-Cutting-Ribbons/ -# tags: easy / medium, array, sorting, binary search +# tags: medium, array, sorting, binary search, tricky """ You are given an integer array ribbons, where ribbons[i] represents the length of the i-th ribbon, and an integer k. You may cut any of the ribbons into any number of segments of positive integer lengths, or perform no cuts at all. diff --git a/200_Number_of_Islands.py b/200_Number_of_Islands.py index 1816662..f2abb5f 100644 --- a/200_Number_of_Islands.py +++ b/200_Number_of_Islands.py @@ -7,10 +7,11 @@ Used: - TikTok: https://www.1point3acres.com/bbs/thread-1064304-1-1.html +- Disney: https://www.1point3acres.com/bbs/thread-1067063-1-1.html """ # https://leetcode.com/problems/number-of-islands/description/ -# tags: medium +# tags: medium, matrix, bfs, dfs """ Given an m x n 2D binary grid grid which represents a map of '1's (land) and '0's (water), return the number of islands. @@ -46,3 +47,78 @@ 1 <= m, n <= 300 grid[i][j] is '0' or '1'. """ + + +# 20240603 +from collections import deque + +def num_of_islands(matrix): + if not matrix or not matrix[0]: + return 0 + + m, n = len(matrix), len(matrix[0]) + visited = [[0] * n for _ in range(m)] + count = 0 + + def valid(i, j): + return 0 <= i < m and 0 <= j < n + + def identify(i, j): + + def process(x, y): + if valid(x, y) and not visited[x][y]: + if matrix[x][y] == '1': + queue.append((x, y)) + + queue = deque([(i, j)]) + while queue: + ci, cj = queue.popleft() + visited[ci][cj] = 1 + process(ci + 1, cj) + process(ci - 1, cj) + process(ci, cj + 1) + process(ci, cj - 1) + + + for i in range(m): + for j in range(n): + if matrix[i][j] == '1' and not visited[i][j]: + identify(i, j) + count += 1 + + return count + +# alternative +def num_of_islands(matrix): + if not matrix or not matrix[0]: + return 0 + + m, n = len(matrix), len(matrix[0]) + visited = [[0] * n for _ in range(m)] + + def valid(i, j): + return 0 <= i < m and 0 <= j < n + + def identify(i, j): + + def process(x, y): + if valid(x, y) and not visited[x][y]: + if matrix[x][y] == '1': + queue.append((x, y)) + + queue = deque([(i, j)]) + while queue: + ci, cj = queue.popleft() + visited[ci][cj] = 1 + process(ci + 1, cj) + process(ci - 1, cj) + process(ci, cj + 1) + process(ci, cj - 1) + + return 1 + + return sum(identify(i, j) + for i in range(m) + for j in range(n) + if matrix[i][j] == '1' and not visited[i][j] + ) From f6c6879970e85ee91f1be9cfd163f23f863c98bf Mon Sep 17 00:00:00 2001 From: Shengwei Li Date: Wed, 5 Jun 2024 11:20:25 -0400 Subject: [PATCH 09/16] new problems (pinterest, ronbinhood, disney) --- 1055_Shortest_Way_to_Form_String.py | 79 +++++++++++ 1801_Number_of_Orders_in_the_Backlog.py | 81 +++++++++++ 200_Number_of_Islands.py | 2 +- 322_Coin_Change.py | 173 ++++++++++++++++++++++++ 4 files changed, 334 insertions(+), 1 deletion(-) create mode 100644 1055_Shortest_Way_to_Form_String.py create mode 100644 1801_Number_of_Orders_in_the_Backlog.py create mode 100644 322_Coin_Change.py diff --git a/1055_Shortest_Way_to_Form_String.py b/1055_Shortest_Way_to_Form_String.py new file mode 100644 index 0000000..b49bf32 --- /dev/null +++ b/1055_Shortest_Way_to_Form_String.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +1055. Shortest Way to Form String + +Created by Shengwei on 2025-05-30. + +Used: +- Pinterest: https://www.1point3acres.com/bbs/thread-1068940-1-1.html +""" + +# locked +# tags: medium, string, shortest + +""" +From any string, we can form a subsequence of that string by deleting some number of characters (possibly no deletions). + +Given two strings source and target, return the minimum number of subsequences of source such that their concatenation equals target. If the task is impossible, return -1. + + + +Example 1: + +Input: source = "abc", target = "abcbc" +Output: 2 +Explanation: The target "abcbc" can be formed by "abc" and "bc", which are subsequences of source "abc". + +Example 2: + +Input: source = "abc", target = "acdbc" +Output: -1 +Explanation: The target string cannot be constructed from the subsequences of source string due to the character "d" in target string. + +Example 3: + +Input: source = "xyz", target = "xzyxz" +Output: 3 +Explanation: The target string can be constructed as follows "xz" + "y" + "xz". + + +Constraints: + +Both the source and target strings consist of only lowercase English letters from "a"-"z". +The lengths of source and target string are between 1 and 1000. + +""" + +def form(source, target): + ti = 0 + res = 0 + while ti < len(target): + si = 0 + old_ti = ti + res += 1 + while si < len(source): + if source[si] == target[ti]: + ti += 1 + if ti == len(target): + return res + si += 1 + if ti == old_ti: + return -1 + return -1 + +# alternative +def form(source, target): + ti = 0 + res = 0 + while ti < len(target): + si = 0 + old_ti = ti + res += 1 + while si < len(source) and ti < len(target): + if source[si] == target[ti]: + ti += 1 + si += 1 + if ti == old_ti: + return -1 + return res diff --git a/1801_Number_of_Orders_in_the_Backlog.py b/1801_Number_of_Orders_in_the_Backlog.py new file mode 100644 index 0000000..de9c6c1 --- /dev/null +++ b/1801_Number_of_Orders_in_the_Backlog.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +1801. Number of Orders in the Backlog + +Created by Shengwei on 2024-06-04. + +Used: +- Robinhood: https://www.1point3acres.com/bbs/thread-1067919-1-1.html +""" + +# https://leetcode.com/problems/number-of-orders-in-the-backlog/description/ +# tags: medium, heap, array, logic + +""" +You are given a 2D integer array orders, where each orders[i] = [pricei, amounti, orderTypei] denotes that amounti orders have been placed of type orderTypei at the price pricei. The orderTypei is: + +0 if it is a batch of buy orders, or +1 if it is a batch of sell orders. +Note that orders[i] represents a batch of amounti independent orders with the same price and order type. All orders represented by orders[i] will be placed before all orders represented by orders[i+1] for all valid i. + +There is a backlog that consists of orders that have not been executed. The backlog is initially empty. When an order is placed, the following happens: + +If the order is a buy order, you look at the sell order with the smallest price in the backlog. If that sell order's price is smaller than or equal to the current buy order's price, they will match and be executed, and that sell order will be removed from the backlog. Else, the buy order is added to the backlog. +Vice versa, if the order is a sell order, you look at the buy order with the largest price in the backlog. If that buy order's price is larger than or equal to the current sell order's price, they will match and be executed, and that buy order will be removed from the backlog. Else, the sell order is added to the backlog. +Return the total amount of orders in the backlog after placing all the orders from the input. Since this number can be large, return it modulo 109 + 7. + + +Example 1: + +Input: orders = [[10,5,0],[15,2,1],[25,1,1],[30,4,0]] +Output: 6 +Explanation: Here is what happens with the orders: +- 5 orders of type buy with price 10 are placed. There are no sell orders, so the 5 orders are added to the backlog. +- 2 orders of type sell with price 15 are placed. There are no buy orders with prices larger than or equal to 15, so the 2 orders are added to the backlog. +- 1 order of type sell with price 25 is placed. There are no buy orders with prices larger than or equal to 25 in the backlog, so this order is added to the backlog. +- 4 orders of type buy with price 30 are placed. The first 2 orders are matched with the 2 sell orders of the least price, which is 15 and these 2 sell orders are removed from the backlog. The 3rd order is matched with the sell order of the least price, which is 25 and this sell order is removed from the backlog. Then, there are no more sell orders in the backlog, so the 4th order is added to the backlog. +Finally, the backlog has 5 buy orders with price 10, and 1 buy order with price 30. So the total number of orders in the backlog is 6. + + +Example 2: + +Input: orders = [[7,1000000000,1],[15,3,0],[5,999999995,0],[5,1,1]] +Output: 999999984 +Explanation: Here is what happens with the orders: +- 109 orders of type sell with price 7 are placed. There are no buy orders, so the 109 orders are added to the backlog. +- 3 orders of type buy with price 15 are placed. They are matched with the 3 sell orders with the least price which is 7, and those 3 sell orders are removed from the backlog. +- 999999995 orders of type buy with price 5 are placed. The least price of a sell order is 7, so the 999999995 orders are added to the backlog. +- 1 order of type sell with price 5 is placed. It is matched with the buy order of the highest price, which is 5, and that buy order is removed from the backlog. +Finally, the backlog has (1000000000-3) sell orders with price 7, and (999999995-1) buy orders with price 5. So the total number of orders = 1999999991, which is equal to 999999984 % (109 + 7). +""" + +# https://leetcode.com/problems/number-of-orders-in-the-backlog/solutions/1119992/java-c-python-priority-queue/ + +class Solution: + def getNumberOfBacklogOrders(self, orders: List[List[int]]) -> int: + import heapq + + sell = [] + buy = [] + for p, c, a in orders: + if a == 0: + heapq.heappush(buy, [-p, c]) + else: + heapq.heappush(sell, [p, c]) + + # note: this is within the for loop; not ideal for capital gain though + while sell and buy and sell[0][0] <= -buy[0][0]: + # print('sell:', sell) + # print('buy:', buy) + count = min(buy[0][1], sell[0][1]) + buy[0][1] -= count + sell[0][1] -= count + if buy[0][1] == 0: + heapq.heappop(buy) + if sell[0][1] == 0: + heapq.heappop(sell) + + # print('sell:', sell) + # print('buy:', buy) + return sum(c for _, c in buy + sell) % (10**9 + 7) diff --git a/200_Number_of_Islands.py b/200_Number_of_Islands.py index f2abb5f..f414ec0 100644 --- a/200_Number_of_Islands.py +++ b/200_Number_of_Islands.py @@ -3,7 +3,7 @@ """ 200. Number of Islands -Created by Shengwei on 2025-05-08. +Created by Shengwei on 2024-05-08. Used: - TikTok: https://www.1point3acres.com/bbs/thread-1064304-1-1.html diff --git a/322_Coin_Change.py b/322_Coin_Change.py new file mode 100644 index 0000000..5f16d1a --- /dev/null +++ b/322_Coin_Change.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +322. Coin Change + +Created by Shengwei on 2025-06-04. + +Used: +- Disney: https://www.1point3acres.com/bbs/thread-667201-1-1.html +""" + +# https://leetcode.com/problems/coin-change/description/ +# tags: medium, array, dp, bfs + +""" +You are given an integer array coins representing coins of different denominations and an integer amount representing a total amount of money. + +Return the fewest number of coins that you need to make up that amount. If that amount of money cannot be made up by any combination of the coins, return -1. + +You may assume that you have an infinite number of each kind of coin. + + +Example 1: + +Input: coins = [1,2,5], amount = 11 +Output: 3 +Explanation: 11 = 5 + 5 + 1 +Example 2: + +Input: coins = [2], amount = 3 +Output: -1 +Example 3: + +Input: coins = [1], amount = 0 +Output: 0 + + +Constraints: + +1 <= coins.length <= 12 +1 <= coins[i] <= 231 - 1 +0 <= amount <= 104 +""" + +# 20240605 - bottom-up dp +class Solution: + + def coinChange(self, coins: List[int], amount: int) -> int: + """gradually increase amount `i` and find the count for it in cache; + the count for the current `i` (and all to the left) would be already the minimum + + add possible coins to the amount `i` and update the count for the new amount + (on the right of amount `i` in cache) if necessary + """ + if amount == 0: return 0 + if amount in coins: return 1 + + cache = {c: 1 for c in coins} + for i in range(min(coins), amount): + count = cache.get(i) + if not count: + continue + + for c in coins: + next_amount = i + c + current = cache.get(next_amount, count + 1) + cache[next_amount] = min(current, count + 1) + + return cache.get(amount, -1) + +# alternative +class Solution: + + def coinChange(self, coins: List[int], amount: int) -> int: + if amount == 0: return 0 + if amount in coins: return 1 + + cache = {c: 1 for c in coins} + for i in range(min(coins) + 1, amount + 1): + for c in coins: + if i - c not in cache: + continue + possible_count = cache[i - c] + 1 + current = cache.get(i, possible_count) + cache[i] = min(current, possible_count) + + return cache.get(amount, -1) + + +# 20240604 - top-down dp +class Solution: + + def __init__(self): + self.cache = {} + + def coinChange(self, coins: List[int], amount: int) -> int: + if amount in self.cache: + return self.cache[amount] + + if amount == 0: + return 0 + + if amount < min(coins): + return -1 + + min_count = float('inf') + for c in coins: + sub = self.coinChange(coins, amount - c) + 1 + if 0 < sub < min_count: + min_count = sub + + if min_count == float('inf'): + min_count = -1 + self.cache[amount] = min_count + return min_count + +# alternative - use @cache +class Solution: + + def coinChange(self, coins: List[int], amount: int) -> int: + + @cache + def check(total): + if total == 0: + return 0 + + if total < min(coins): + return -1 + + min_count = float('inf') + for c in coins: + sub = check(total - c) + 1 + if 0 < sub < min_count: + min_count = sub + + return -1 if min_count == float('inf') else min_count + + return check(amount) + + +# 20181014 - bfs +class Solution(object): + def coinChange(self, coins, amount): + """ + :type coins: List[int] + :type amount: int + :rtype: int + """ + if not coins: return -1 + if not amount: return 0 + + coins.sort() + count = 0 + cache = set([amount]) + stack = [amount] + while stack: + holder = [] + count += 1 + for total in stack: + for coin in coins: + if coin > total: + break + + if coin == total: + return count + + rest = total - coin + if rest not in cache: + cache.add(rest) + holder.append(rest) + + stack = holder + return -1 From 68c2e196396d75f1c6fa40d59537f25822c3d450 Mon Sep 17 00:00:00 2001 From: Shengwei Li Date: Sun, 16 Jun 2024 20:46:28 -0400 Subject: [PATCH 10/16] new problem 787 --- 322_Coin_Change.py | 2 + 787_Cheapest_Flights_Within_K_Stops.py | 161 +++++++++++++++++++++++++ first150/6_zigzag_conversion.py | 5 + 3 files changed, 168 insertions(+) create mode 100644 787_Cheapest_Flights_Within_K_Stops.py diff --git a/322_Coin_Change.py b/322_Coin_Change.py index 5f16d1a..2ad174a 100644 --- a/322_Coin_Change.py +++ b/322_Coin_Change.py @@ -77,6 +77,8 @@ def coinChange(self, coins: List[int], amount: int) -> int: cache = {c: 1 for c in coins} for i in range(min(coins) + 1, amount + 1): + # alternative of `for` loop: + # cache[i] = min(cache.get(i - c, float('inf')) + 1 for c in coins) for c in coins: if i - c not in cache: continue diff --git a/787_Cheapest_Flights_Within_K_Stops.py b/787_Cheapest_Flights_Within_K_Stops.py new file mode 100644 index 0000000..d000971 --- /dev/null +++ b/787_Cheapest_Flights_Within_K_Stops.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +787. Cheapest Flights Within K Stops + +Created by Shengwei on 2024-06-16. + +Used: +- Snap: https://www.1point3acres.com/bbs/thread-1068337-1-1.html +""" + +# https://leetcode.com/problems/cheapest-flights-within-k-stops/description/ +# tags: medium, graph, Dijkstra, bfs, dfs, lowest + +""" +There are n cities connected by some number of flights. You are given an array flights where flights[i] = [fromi, toi, pricei] indicates that there is a flight from city fromi to city toi with cost pricei. + +You are also given three integers src, dst, and k, return the cheapest price from src to dst with at most k stops. If there is no such route, return -1. + + + +Example 1: + +Input: n = 4, flights = [[0,1,100],[1,2,100],[2,0,100],[1,3,600],[2,3,200]], src = 0, dst = 3, k = 1 +Output: 700 +Explanation: +The graph is shown above. +The optimal path with at most 1 stop from city 0 to 3 is marked in red and has cost 100 + 600 = 700. +Note that the path through cities [0,1,2,3] is cheaper but is invalid because it uses 2 stops. + + +Example 2: + +Input: n = 3, flights = [[0,1,100],[1,2,100],[0,2,500]], src = 0, dst = 2, k = 1 +Output: 200 +Explanation: +The graph is shown above. +The optimal path with at most 1 stop from city 0 to 2 is marked in red and has cost 100 + 100 = 200. + + +Example 3: + +Input: n = 3, flights = [[0,1,100],[1,2,100],[0,2,500]], src = 0, dst = 2, k = 0 +Output: 500 +Explanation: +The graph is shown above. +The optimal path with no stops from city 0 to 2 is marked in red and has cost 500. + + +Constraints: + +1 <= n <= 100 +0 <= flights.length <= (n * (n - 1) / 2) +flights[i].length == 3 +0 <= fromi, toi < n +fromi != toi +1 <= pricei <= 104 +There will not be any multiple flights between two cities. +0 <= src, dst, k < n +src != dst +""" + +# https://leetcode.com/problems/cheapest-flights-within-k-stops/solutions/115541/java-c-python-priority-queue-solution-tle-now/ +# https://leetcode.com/problems/cheapest-flights-within-k-stops/solutions/361711/java-dfs-bfs-bellman-ford-dijkstra-s/ + +# Dijkstra +# Note: cannot set visited without stops; need to also double check higher prices with fewer stops +class Solution: + def findCheapestPrice(self, n: int, flights: List[List[int]], src: int, dst: int, k: int) -> int: + + if src == dst: return 0 + + import heapq + + refs = defaultdict(dict) + for s, d, p in flights: + refs[s][d] = p + + cache = [(0, src, 0)] + visited = {} + while cache: + price, city, n_stop = heapq.heappop(cache) + + # note: check this outside the `for` loop + # as there could be multiple route to dst even in one loop + if city == dst: + return price + + # process it only when the # of stops to city is fewer + if n_stop >= visited.get(city, n_stop + 1): + continue + + if n_stop <= k: + for c, p in refs[city].items(): + heapq.heappush(cache, (price + p, c, n_stop + 1)) + + # note: only mark city visited after outbounds are processed; + # there could be cases when city was reached with k+1 stops + visited[city] = n_stop + + return -1 + + +# TLM (20240616) +class Solution: + def findCheapestPrice(self, n: int, flights: List[List[int]], src: int, dst: int, k: int) -> int: + + if src == dst: return 0 + + import heapq + + refs = defaultdict(dict) + for s, d, p in flights: + refs[s][d] = p + + cache = [(0, src, 0)] + while cache: + price, city, n_stop = heapq.heappop(cache) + # print(city, price, n_stop) + + # note: check this outside the `for` loop + # as there could be multiple route to dst even in one loop + if city == dst: + return price + + if n_stop <= k: + for c, p in refs[city].items(): + heapq.heappush(cache, (price + p, c, n_stop + 1)) + # print(cache) + + return -1 + + +# BFS -- Memory Limit Exceeded +from collections import defaultdict, deque + +class Solution: + def findCheapestPrice(self, n: int, flights: List[List[int]], src: int, dst: int, k: int) -> int: + if src == dst: return 0 + + store = defaultdict(dict) + for s, d, p in flights: + store[s][d] = p + + check = deque([(src, 0)]) + min_p = float('inf') + stop = 0 + while check and stop <= k: + size = len(check) + for _ in range(size): + place, price = check.popleft() + for d, p in store[place].items(): + np = price + p + if d == dst: + min_p = min(min_p, np) + elif np < min_p: + check.append((d, np)) + + stop += 1 + + return -1 if min_p == float('inf') else min_p diff --git a/first150/6_zigzag_conversion.py b/first150/6_zigzag_conversion.py index 90a38bc..48661a6 100755 --- a/first150/6_zigzag_conversion.py +++ b/first150/6_zigzag_conversion.py @@ -36,6 +36,11 @@ def convert(self, s, nRows): index = row while index < len(s): res += s[index] + + # add extra char inbetween two vertical columns: + # for the first row and the last row (distance == 0), + # there is no char inbetween two vertical columns, + # so exclude these two rows distance = nRows - 1 - row if row > 0 and distance > 0: next_index = index + 2 * distance From 4f7140f5844dd199c3d37fd50d10d10aaae4c780 Mon Sep 17 00:00:00 2001 From: Shengwei Li Date: Mon, 17 Jun 2024 11:53:45 -0400 Subject: [PATCH 11/16] new problem 227 (Snap) --- 227_Basic_Calculator_II.py | 119 +++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 227_Basic_Calculator_II.py diff --git a/227_Basic_Calculator_II.py b/227_Basic_Calculator_II.py new file mode 100644 index 0000000..7496cd1 --- /dev/null +++ b/227_Basic_Calculator_II.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +227. Basic Calculator II + +Created by Shengwei on 2024-06-17. + +Used: +- Snap: https://www.1point3acres.com/bbs/thread-1064167-1-1.html +""" + +# https://leetcode.com/problems/basic-calculator-ii/description/ +# tags: medium / hard, stack, logic + +""" +Given a string s which represents an expression, evaluate this expression and return its value. + +The integer division should truncate toward zero. + +You may assume that the given expression is always valid. All intermediate results will be in the range of [-231, 231 - 1]. + +Note: You are not allowed to use any built-in function which evaluates strings as mathematical expressions, such as eval(). + + + +Example 1: + +Input: s = "3+2*2" +Output: 7 + + +Example 2: + +Input: s = " 3/2 " +Output: 1 + + +Example 3: + +Input: s = " 3+5 / 2 " +Output: 5 + + +Constraints: + +1 <= s.length <= 3 * 105 +s consists of integers and operators ('+', '-', '*', '/') separated by some number of spaces. +s represents a valid expression. +All the integers in the expression are non-negative integers in the range [0, 231 - 1]. +The answer is guaranteed to fit in a 32-bit integer. +""" + +# https://leetcode.com/problems/basic-calculator-ii/solutions/63076/python-short-solution-with-stack/ +# https://leetcode.com/problems/basic-calculator-ii/solutions/63004/17-lines-c-easy-20-ms/ <-- non-stack version + + +# non-stack version +class Solution: + def calculate(self, s: str) -> int: + res = last = cur = 0 + sign = '+' + + for i in range(len(s) + 1): + if i < len(s): + c = s[i] + if c.isdigit(): + cur = cur * 10 + int(c) + continue + if c == ' ': + continue + + # print(res, last, sign, cur) + if sign in ('+', '-'): + res += last + last = cur if sign == '+' else -cur + else: + # more complicated in Python as -5 // 2 = -3 + if cur > 0: # has to check cur here, otherwise throw divided by zero error (should never happen) + tmp = last // cur if last > 0 or last % cur == 0 else last // cur + 1 + else: + tmp = 0 + last = last * cur if sign == '*' else tmp + + sign = c + cur = 0 + + return res + last + + +# alternative: use flag to circumvent negative integer divide issue in Python +class Solution: + def calculate(self, s: str) -> int: + res = last = cur = 0 + sign = '+' + negative = False + + for i in range(len(s) + 1): + if i < len(s): + c = s[i] + if c.isdigit(): + cur = cur * 10 + int(c) + continue + if c == ' ': + continue + + # print(res, last, '-' if negative else '+', sign, cur) + if sign in ('+', '-'): + res += last if not negative else -last + # note: update the flag only after it's used and current sign in ('+', '-') + negative = sign == '-' + last = cur + else: + last = last * cur if sign == '*' else last // cur + + sign = c + cur = 0 + + # note: don't forget to use `negative` here as well + return res + last if not negative else res - last From ee1c9d82cf65b6485c9135dcc77696aa213682fa Mon Sep 17 00:00:00 2001 From: Shengwei Li Date: Sun, 23 Jun 2024 18:26:05 -0400 Subject: [PATCH 12/16] new problems 465 and 1631 (snap) --- 1631_Path_With_Minimum_Effort.py | 120 +++++++++++++++++++++++++++++++ 465_Optimal_Account_Balancing.py | 56 +++++++++++++++ 2 files changed, 176 insertions(+) create mode 100644 1631_Path_With_Minimum_Effort.py create mode 100644 465_Optimal_Account_Balancing.py diff --git a/1631_Path_With_Minimum_Effort.py b/1631_Path_With_Minimum_Effort.py new file mode 100644 index 0000000..0578c63 --- /dev/null +++ b/1631_Path_With_Minimum_Effort.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +1631. Path With Minimum Effort + +Created by Shengwei on 2024-06-23. + +Used: +- Snap: https://www.1point3acres.com/bbs/thread-1047384-1-1.html +""" + +# https://leetcode.com/problems/path-with-minimum-effort/description/ +# tags: medium / hard, matrix, max, min, Dijikstra + +""" +You are a hiker preparing for an upcoming hike. You are given heights, a 2D array of size rows x columns, where heights[row][col] represents the height of cell (row, col). You are situated in the top-left cell, (0, 0), and you hope to travel to the bottom-right cell, (rows-1, columns-1) (i.e., 0-indexed). You can move up, down, left, or right, and you wish to find a route that requires the minimum effort. + +A route's effort is the maximum absolute difference in heights between two consecutive cells of the route. + +Return the minimum effort required to travel from the top-left cell to the bottom-right cell. + + + +Example 1: + +Input: heights = [[1,2,2],[3,8,2],[5,3,5]] +Output: 2 +Explanation: The route of [1,3,5,3,5] has a maximum absolute difference of 2 in consecutive cells. +This is better than the route of [1,2,2,2,5], where the maximum absolute difference is 3. + +Example 2: + +Input: heights = [[1,2,3],[3,8,4],[5,3,5]] +Output: 1 +Explanation: The route of [1,2,3,4,5] has a maximum absolute difference of 1 in consecutive cells, which is better than route [1,3,5,3,5]. + +Example 3: + +Input: heights = [[1,2,1,1,1],[1,2,1,2,1],[1,2,1,2,1],[1,2,1,2,1],[1,1,1,2,1]] +Output: 0 +Explanation: This route does not require any effort. + + +Constraints: + +rows == heights.length +columns == heights[i].length +1 <= rows, columns <= 100 +1 <= heights[i][j] <= 106 +""" + +# https://leetcode.com/problems/path-with-minimum-effort/solutions/909017/java-python-dijikstra-binary-search-clean-concise/ +class Solution: + def minimumEffortPath(self, heights: List[List[int]]) -> int: + + from heapq import heappush as push + from heapq import heappop as pop + from math import inf + + if not heights: return 0 + + m, n = len(heights), len(heights[0]) + cur_efforts = [(0, (0, 0))] # effort, (x, y) + # min efforts so far, may not be the global min, to save extra push + min_efforts = {} # key: (x, y), value: min_effort + direction = (0, 1, 0, -1, 0) + + while cur_efforts: + effort, (x, y) = pop(cur_efforts) + # skip if has processed (x, y) with smaller effort + # note: in this case, cannot skip effort with the same effort as added earlier + if effort > min_efforts.get((x, y), inf): continue + if (x, y) == (m - 1, n - 1): + return effort + + for xd, yd in zip(direction, direction[1:]): + nx, ny = x + xd, y + yd + if 0 <= nx < m and 0 <= ny < n: + n_effort = max(effort, abs(heights[x][y] - heights[nx][ny])) + # track currently min effort for (nx, ny) so far, even if it's not the global min yet + if n_effort < min_efforts.get((nx, ny), inf): + min_efforts[(nx, ny)] = n_effort + push(cur_efforts, (n_effort, (nx, ny))) + + # should never be here + return -1 + +# comparison +class Solution: + def minimumEffortPath(self, heights: List[List[int]]) -> int: + + from heapq import heappush as push + from heapq import heappop as pop + from math import inf + + if not heights: return 0 + + m, n = len(heights), len(heights[0]) + cur_efforts = [(0, (0, 0))] # effort, (x, y) + min_efforts = {} # use this to track global min only + direction = (0, 1, 0, -1, 0) + + while cur_efforts: + effort, (x, y) = pop(cur_efforts) + # skip if has processed (x, y) with equal or smaller effort + if effort >= min_efforts.get((x, y), inf): continue + if (x, y) == (m - 1, n - 1): + return effort + # this effort must be the global min now + min_efforts[(x, y)] = effort + + for xd, yd in zip(direction, direction[1:]): + nx, ny = x + xd, y + yd + if 0 <= nx < m and 0 <= ny < n: + n_effort = max(effort, abs(heights[x][y] - heights[nx][ny])) + # theoretically no check here also works, but waste space and time + if n_effort < min_efforts.get((nx, ny), inf): + push(cur_efforts, (n_effort, (nx, ny))) + + return -1 diff --git a/465_Optimal_Account_Balancing.py b/465_Optimal_Account_Balancing.py new file mode 100644 index 0000000..3f17210 --- /dev/null +++ b/465_Optimal_Account_Balancing.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +465. Optimal Account Balancing + +Created by Shengwei on 2024-06-17. + +Used: +- Snap (asked): https://www.1point3acres.com/bbs/thread-767545-1-1.html +""" + +# locked +# tags: hard, bit manipulation, dp + +""" +A group of friends went on holiday and sometimes lent each other money. For example, Alice paid for Bill's lunch for $10. Then later Chris gave Alice $5 for a taxi ride. We can model each transaction as a tuple (x, y, z) which means person x gave person y $z. Assuming Alice, Bill, and Chris are person 0, 1, and 2 respectively (0, 1, 2 are the person's ID), the transactions can be represented as [[0, 1, 10], [2, 0, 5]]. + +Given a list of transactions between a group of people, return the minimum number of transactions required to settle the debt. + +Note: + +A transaction will be given as a tuple (x, y, z). Note that x ≠ y and z > 0. +Person's IDs may not be linear, e.g. we could have the persons 0, 1, 2 or we could also have the persons 0, 2, 6. +Example 1: + +Input: +[[0,1,10], [2,0,5]] + +Output: +2 + +Explanation: +Person #0 gave person #1 $10. +Person #2 gave person #0 $5. + +Two transactions are needed. One way to settle the debt is person #1 pays person #0 and #2 $5 each. +Example 2: + +Input: +[[0,1,10], [1,0,1], [1,2,5], [2,0,5]] + +Output: +1 + +Explanation: +Person #0 gave person #1 $10. +Person #1 gave person #0 $1. +Person #1 gave person #2 $5. +Person #2 gave person #0 $5. + +Therefore, person #1 only need to give person #0 $4, and all debt is settled. +""" + +# https://leetcode.ca/all/465.html +# https://algo.monster/liteproblems/465 +# https://en.wikipedia.org/wiki/Subset_sum_problem From f929c0f087651a87719d45a3f23b21f70e489cc2 Mon Sep 17 00:00:00 2001 From: Shengwei Li Date: Mon, 24 Jun 2024 17:49:53 -0400 Subject: [PATCH 13/16] new problems (snap) --- 2097_Valid_Arrangement_of_Pairs.py | 68 +++++++++++++++++++++ 218_The_Skyline_Problem.py | 97 ++++++++++++++++++++++++++++++ 695_Max_Area_of_Island.py | 94 +++++++++++++++++++++++++++++ 3 files changed, 259 insertions(+) create mode 100644 2097_Valid_Arrangement_of_Pairs.py create mode 100644 218_The_Skyline_Problem.py create mode 100644 695_Max_Area_of_Island.py diff --git a/2097_Valid_Arrangement_of_Pairs.py b/2097_Valid_Arrangement_of_Pairs.py new file mode 100644 index 0000000..c532dde --- /dev/null +++ b/2097_Valid_Arrangement_of_Pairs.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +2097. Valid Arrangement of Pairs + +Created by Shengwei on 2024-06-24. + +Used: +- Snap: https://www.1point3acres.com/bbs/thread-1043575-1-1.html +- Snap: https://www.1point3acres.com/bbs/thread-1062856-1-1.html +""" + +# https://leetcode.com/problems/valid-arrangement-of-pairs/description/ +# tags: hard, graph, dfs, tricky + +""" +You are given a 0-indexed 2D integer array pairs where pairs[i] = [starti, endi]. An arrangement of pairs is valid if for every index i where 1 <= i < pairs.length, we have endi-1 == starti. + +Return any valid arrangement of pairs. + +Note: The inputs will be generated such that there exists a valid arrangement of pairs. + + + +Example 1: + +Input: pairs = [[5,1],[4,5],[11,9],[9,4]] +Output: [[11,9],[9,4],[4,5],[5,1]] +Explanation: +This is a valid arrangement since endi-1 always equals starti. +end0 = 9 == 9 = start1 +end1 = 4 == 4 = start2 +end2 = 5 == 5 = start3 + + +Example 2: + +Input: pairs = [[1,3],[3,2],[2,1]] +Output: [[1,3],[3,2],[2,1]] +Explanation: +This is a valid arrangement since endi-1 always equals starti. +end0 = 3 == 3 = start1 +end1 = 2 == 2 = start2 +The arrangements [[2,1],[1,3],[3,2]] and [[3,2],[2,1],[1,3]] are also valid. + + +Example 3: + +Input: pairs = [[1,2],[1,3],[2,1]] +Output: [[1,2],[2,1],[1,3]] +Explanation: +This is a valid arrangement since endi-1 always equals starti. +end0 = 2 == 2 = start1 +end1 = 1 == 1 = start2 + + +Constraints: + +1 <= pairs.length <= 105 +pairs[i].length == 2 +0 <= starti, endi <= 109 +starti != endi +No two pairs are exactly the same. +There exists a valid arrangement of pairs. +""" + +# https://leetcode.com/problems/valid-arrangement-of-pairs/solutions/1611983/python-o-pairs-length-hierholzer-s-algorithm/ +# https://en.wikipedia.org/wiki/Eulerian_path (#Hierholzer's_algorithm) diff --git a/218_The_Skyline_Problem.py b/218_The_Skyline_Problem.py new file mode 100644 index 0000000..9d295a4 --- /dev/null +++ b/218_The_Skyline_Problem.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +218. The Skyline Problem + +Created by Shengwei on 2024-06-24. + +Used: +- Snap: https://www.1point3acres.com/bbs/thread-1063723-1-1.html +""" + +# https://leetcode.com/problems/the-skyline-problem/description/ +# tags: hard, array, heap, height, merge + +""" +A city's skyline is the outer contour of the silhouette formed by all the buildings in that city when viewed from a distance. Given the locations and heights of all the buildings, return the skyline formed by these buildings collectively. + +The geometric information of each building is given in the array buildings where buildings[i] = [lefti, righti, heighti]: + +lefti is the x coordinate of the left edge of the ith building. +righti is the x coordinate of the right edge of the ith building. +heighti is the height of the ith building. +You may assume all buildings are perfect rectangles grounded on an absolutely flat surface at height 0. + +The skyline should be represented as a list of "key points" sorted by their x-coordinate in the form [[x1,y1],[x2,y2],...]. Each key point is the left endpoint of some horizontal segment in the skyline except the last point in the list, which always has a y-coordinate 0 and is used to mark the skyline's termination where the rightmost building ends. Any ground between the leftmost and rightmost buildings should be part of the skyline's contour. + +Note: There must be no consecutive horizontal lines of equal height in the output skyline. For instance, [...,[2 3],[4 5],[7 5],[11 5],[12 7],...] is not acceptable; the three lines of height 5 should be merged into one in the final output as such: [...,[2 3],[4 5],[12 7],...] + + + +Example 1: + +Input: buildings = [[2,9,10],[3,7,15],[5,12,12],[15,20,10],[19,24,8]] +Output: [[2,10],[3,15],[7,12],[12,0],[15,10],[20,8],[24,0]] +Explanation: +Figure A shows the buildings of the input. +Figure B shows the skyline formed by those buildings. The red points in figure B represent the key points in the output list. + + +Example 2: + +Input: buildings = [[0,2,3],[2,5,3]] +Output: [[0,3],[5,0]] + + +Constraints: + +1 <= buildings.length <= 104 +0 <= lefti < righti <= 231 - 1 +1 <= heighti <= 231 - 1 +buildings is sorted by lefti in non-decreasing order. +""" + +# https://leetcode.com/problems/the-skyline-problem/solutions/2094329/c-easiest-explanation-ever-guaranteed-beginner-friendly-detailed-o-nlogn/ +# https://leetcode.com/problems/the-skyline-problem/solutions/61261/11-line-python-solution-with-max-heap-easy-to-understand/ -- O(nlogn) because of sort + +""" +Test cases: + - [[0,2,3],[2,5,3]]: left must come out first when heights are the same + - [[1,2,1],[1,2,2],[1,2,3]]: must use -H to sort highest first +""" + +class Solution: + def getSkyline(self, buildings: List[List[int]]) -> List[List[int]]: + from collections import defaultdict + from heapq import heappush as push + from heapq import heappop as pop + from math import inf + + # use negative height here, so highest at the same pos will come first + left = sorted([(L, -H) for L, _, H in buildings]) + right = sorted([(R, H) for _, R, H in buildings]) + max_heap = [0] + checks = defaultdict(int) + res = [] + li = ri = 0 + while ri < len(right): + # when the pos in both lists are the same, should process left first. + # in case they are the same height, processing right will remove + # the height and cause false result + if li < len(left) and left[li][0] <= right[ri][0]: + x, h = left[li] + li += 1 + push(max_heap, h) + else: + x, h = right[ri] + ri += 1 + checks[-h] += 1 + + while checks[max_heap[0]] > 0: + checks[pop(max_heap)] -= 1 + + curr_max_height = -max_heap[0] + if not res or res[-1][1] != curr_max_height: + res.append([x, curr_max_height]) + + return res diff --git a/695_Max_Area_of_Island.py b/695_Max_Area_of_Island.py new file mode 100644 index 0000000..d448e02 --- /dev/null +++ b/695_Max_Area_of_Island.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +695. Max Area of Island + +Created by Shengwei on 2024-06-23. + +Used: +- Snap: https://www.1point3acres.com/bbs/thread-1061851-1-1.html +""" + +# https://leetcode.com/problems/max-area-of-island/description/ +# tags: medium, matrix, bfs, dfs, area + +""" +You are given an m x n binary matrix grid. An island is a group of 1's (representing land) connected 4-directionally (horizontal or vertical.) You may assume all four edges of the grid are surrounded by water. + +The area of an island is the number of cells with a value 1 in the island. + +Return the maximum area of an island in grid. If there is no island, return 0. + + +Example 1: + +Input: grid = [[0,0,1,0,0,0,0,1,0,0,0,0,0],[0,0,0,0,0,0,0,1,1,1,0,0,0],[0,1,1,0,1,0,0,0,0,0,0,0,0],[0,1,0,0,1,1,0,0,1,0,1,0,0],[0,1,0,0,1,1,0,0,1,1,1,0,0],[0,0,0,0,0,0,0,0,0,0,1,0,0],[0,0,0,0,0,0,0,1,1,1,0,0,0],[0,0,0,0,0,0,0,1,1,0,0,0,0]] +Output: 6 +Explanation: The answer is not 11, because the island must be connected 4-directionally. + + +Example 2: + +Input: grid = [[0,0,0,0,0,0,0,0]] +Output: 0 +""" + + +# bfs +class Solution: + def maxAreaOfIsland(self, grid: List[List[int]]) -> int: + if not grid or not grid[0]: return 0 + + m, n = len(grid), len(grid[0]) + visited = set() + + def get_area(x, y): + directions = (0, 1, 0, -1, 0) + visited.add((x, y)) + buffer = deque([(x, y)]) # any pos put in buffer are not in visited + area = 0 + while buffer: + i, j = buffer.popleft() + area += 1 + + for di, dj in zip(directions, directions[1:]): + ni, nj = i + di, j + dj + if 0 <= ni < m and 0 <= nj < n and grid[ni][nj] and (ni, nj) not in visited: + # note: must update `visited` within the loop + visited.add((ni, nj)) + buffer.append((ni, nj)) + + return area + + max_area = 0 + for x in range(m): + for y in range(n): + if grid[x][y] and (x, y) not in visited: + max_area = max(max_area, get_area(x, y)) + return max_area + + +# dfs +class Solution: + def maxAreaOfIsland(self, grid: List[List[int]]) -> int: + if not grid or not grid[0]: return 0 + + m, n = len(grid), len(grid[0]) + visited = set() + + def dfs(x, y): + directions = (0, 1, 0, -1, 0) + if 0 <= x < m and 0 <= y < n and grid[x][y] and (x, y) not in visited: + area = 1 + visited.add((x, y)) + for dx, dy in zip(directions, directions[1:]): + area += dfs(x + dx, y + dy) + return area + return 0 + + max_area = 0 + for x in range(m): + for y in range(n): + if grid[x][y] and (x, y) not in visited: + max_area = max(max_area, dfs(x, y)) + return max_area From 750b5b29beac6df51e41922cda530ef0173c3d54 Mon Sep 17 00:00:00 2001 From: Shengwei Li Date: Wed, 26 Jun 2024 22:14:39 -0400 Subject: [PATCH 14/16] updated word search --- word_search.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/word_search.py b/word_search.py index ad163b7..91f6a9f 100755 --- a/word_search.py +++ b/word_search.py @@ -27,6 +27,35 @@ word = "ABCB", -> returns false. """ + +# 20240626 Py3 +class Solution: + def exist(self, board: List[List[str]], word: str) -> bool: + + if not word: return True + if not board or not board[0]: return False + + m, n = len(board), len(board[0]) + directions = ((0, 1), (0, -1), (-1, 0), (1, 0)) + marker = [[0] * n for _ in range(m)] + + def check(row, col, wi): + if 0 <= row < m and 0 <= col < n and not marker[row][col]: + if word[wi] == board[row][col]: + if wi == len(word) - 1: + return True + + marker[row][col] = 1 + if any(check(row + dr, col + dc, wi + 1) for dr, dc in directions): + return True + marker[row][col] = 0 + + return False + + return any(check(r, c, 0) for r in range(m) for c in range(n)) + + +# 20140705 Py2 def match(board, marker, row, col, word): if (row >= 0 and row < len(board) and col >= 0 and col < len(board[row])): From f03e479e96154cc5026a2bb57da25d2faaae17b9 Mon Sep 17 00:00:00 2001 From: Shengwei Li Date: Thu, 27 Jun 2024 12:14:18 -0400 Subject: [PATCH 15/16] new solution for 301 --- 301_Remove_Invalid_Parentheses.py | 44 +++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/301_Remove_Invalid_Parentheses.py b/301_Remove_Invalid_Parentheses.py index bb83d8d..fd314bc 100755 --- a/301_Remove_Invalid_Parentheses.py +++ b/301_Remove_Invalid_Parentheses.py @@ -52,6 +52,50 @@ """ +# 20240627 +class Solution: + def removeInvalidParentheses(self, s: str) -> List[str]: + stack = [] + for i, c in enumerate(s): + if c == ')' and stack and s[stack[-1]] == '(': + stack.pop() + elif c in ('(', ')'): + stack.append(i) + # stack should contain indices of extra parentheses + # print('stack:', stack) + + # loop through stack from left to right upto first '(', or exhaust all extra ')'; + # then loop back from the last to left (using negative index) upto first ')', or exhaust all extra '(' + def remove(str_p, stack_p, p_to_remove): + # print('remove:', str_p, stack_p, p_to_remove) + if stack_p >= len(stack) or stack_p <= -len(stack) - 1 or stack_p < 0 and s[stack[stack_p]] == ')': + removal_set = set(p_to_remove) + yield ''.join(c for i, c in enumerate(s) if i not in removal_set and i - len(s) not in removal_set) + return + + par_p = stack[stack_p] + if stack_p >= 0: + if s[par_p] == ')': + for p in range(str_p, par_p + 1): + if s[p] == ')' and (p == str_p or s[p - 1] != ')'): + p_to_remove.append(p) + for each in remove(p + 1, stack_p + 1, p_to_remove): + yield each + p_to_remove.pop() + else: + for each in remove(-1, -1, p_to_remove): + yield each + else: + par_p = stack[stack_p] - len(s) + for p in range(str_p, par_p - 1, -1): + if s[p] == '(' and (p == str_p or s[p + 1] != '('): + p_to_remove.append(p) + for each in remove(p - 1, stack_p - 1, p_to_remove): + yield each + p_to_remove.pop() + + return list(remove(0, 0, [])) + # https://leetcode.com/problems/remove-invalid-parentheses/discuss/75027/Easy-Short-Concise-and-Fast-Java-DFS-3-ms-solution From 9b7cde5e4aab4905a905af71d1e67ec737c0f65b Mon Sep 17 00:00:00 2001 From: Shengwei Li Date: Fri, 28 Jun 2024 00:18:35 -0400 Subject: [PATCH 16/16] minor: bug fix --- 124_binary_tree_maximum_path_sum.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/124_binary_tree_maximum_path_sum.py b/124_binary_tree_maximum_path_sum.py index 23ae9aa..a30297b 100755 --- a/124_binary_tree_maximum_path_sum.py +++ b/124_binary_tree_maximum_path_sum.py @@ -40,6 +40,7 @@ class Solution: def __init__(self): + # note: not necessarily in `__init__` self.cur_max = 0 def max_sum(self, node): @@ -51,7 +52,7 @@ def max_sum(self, node): # print('left', left_sum) right_sum = self.max_sum(node.right) # print('right', right_sum) - self.cur_max = max(cur_max, left_sum + right_sum + node.value) + self.cur_max = max(self.cur_max, left_sum + right_sum + node.value) # print('cur_max', self.cur_max) return max(0, left_sum + node.value, right_sum + node.value)